#!/usr/bin/python
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
A set of classes (models) each closely representing an XML node in the
metadata_properties.xml file.
Node: Base class for most nodes.
Entry: A node corresponding to <entry> elements.
Clone: A node corresponding to <clone> elements.
Kind: A node corresponding to <dynamic>, <static>, <controls> elements.
InnerNamespace: A node corresponding to a <namespace> nested under a <kind>.
OuterNamespace: A node corresponding to a <namespace> with <kind> children.
Section: A node corresponding to a <section> element.
Enum: A class corresponding an <enum> element within an <entry>
Value: A class corresponding to a <value> element within an Enum
Metadata: Root node that also provides tree construction functionality.
Tag: A node corresponding to a top level <tag> element.
"""
import sys
import itertools
from collections import OrderedDict
class Node(object):
"""
Base class for most nodes that are part of the Metadata graph.
Attributes (Read-Only):
parent: An edge to a parent Node.
name: A string describing the name, usually but not always the 'name'
attribute of the corresponding XML node.
"""
def __init__(self):
self._parent = None
self._name = None
@property
def parent(self):
return self._parent
@property
def name(self):
return self._name
def find_all(self, pred):
"""
Find all descendants that match the predicate.
Args:
pred: a predicate function that acts as a filter for a Node
Yields:
A sequence of all descendants for which pred(node) is true,
in a pre-order visit order.
"""
if pred(self):
yield self
if self._get_children() is None:
return
for i in self._get_children():
for j in i.find_all(pred):
yield j
def find_first(self, pred):
"""
Find the first descendant that matches the predicate.
Args:
pred: a predicate function that acts as a filter for a Node
Returns:
The first Node from find_all(pred), or None if there were no results.
"""
for i in self.find_all(pred):
return i
return None
def find_parent_first(self, pred):
"""
Find the first ancestor that matches the predicate.
Args:
pred: A predicate function that acts as a filter for a Node
Returns:
The first ancestor closest to the node for which pred(node) is true.
"""
for i in self.find_parents(pred):
return i
return None
def find_parents(self, pred):
"""
Find all ancestors that match the predicate.
Args:
pred: A predicate function that acts as a filter for a Node
Yields:
A sequence of all ancestors (closest to furthest) from the node,
where pred(node) is true.
"""
parent = self.parent
while parent is not None:
if pred(parent):
yield parent
parent = parent.parent
def sort_children(self):
"""
Sorts the immediate children in-place.
"""
self._sort_by_name(self._children)
def _sort_by_name(self, what):
what.sort(key=lambda x: x.name)
def _get_name(self):
return lambda x: x.name
# Iterate over all children nodes. None when node doesn't support children.
def _get_children(self):
return (i for i in self._children)
def _children_name_map_matching(self, match=lambda x: True):
d = {}
for i in _get_children():
if match(i):
d[i.name] = i
return d
@staticmethod
def _dictionary_by_name(values):
d = OrderedDict()
for i in values:
d[i.name] = i
return d
def validate_tree(self):
"""
Sanity check the tree recursively, ensuring for a node n, all children's
parents are also n.
Returns:
True if validation succeeds, False otherwise.
"""
succ = True
children = self._get_children()
if children is None:
return True
for child in self._get_children():
if child.parent != self:
print >> sys.stderr, ("ERROR: Node '%s' doesn't match the parent" + \
"(expected: %s, actual %s)") \
%(child, self, child.parent)
succ = False
succ = child.validate_tree() and succ
return succ
def __str__(self):
return "<%s name='%s'>" %(self.__class__, self.name)
class Metadata(Node):
"""
A node corresponding to a <metadata> entry.
Attributes (Read-Only):
parent: An edge to the parent Node. This is always None for Metadata.
outer_namespaces: A sequence of immediate OuterNamespace children.
tags: A sequence of all Tag instances available in the graph.
"""
def __init__(self):
"""
Initialize with no children. Use insert_* functions and then
construct_graph() to build up the Metadata from some source.
"""
# Private
self._entries = []
# kind => { name => entry }
self._entry_map = { 'static': {}, 'dynamic': {}, 'controls': {} }
self._entries_ordered = [] # list of ordered Entry/Clone instances
self._clones = []
# Public (Read Only)
self._parent = None
self._outer_namespaces = None
self._tags = []
@property
def outer_namespaces(self):
if self._outer_namespaces is None:
return None
else:
return (i for i in self._outer_namespaces)
@property
def tags(self):
return (i for i in self._tags)
def _get_properties(self):
for i in self._entries:
yield i
for i in self._clones:
yield i
def insert_tag(self, tag, description=""):
"""
Insert a tag into the metadata.
Args:
tag: A string identifier for a tag.
description: A string description for a tag.
Example:
metadata.insert_tag("BC", "Backwards Compatibility for old API")
Remarks:
Subsequent calls to insert_tag with the same tag are safe (they will
be ignored).
"""
tag_ids = [tg.name for tg in self.tags if tg.name == tag]
if not tag_ids:
self._tags.append(Tag(tag, self, description))
def insert_entry(self, entry):
"""
Insert an entry into the metadata.
Args:
entry: A key-value dictionary describing an entry. Refer to
Entry#__init__ for the keys required/optional.
Remarks:
Subsequent calls to insert_entry with the same entry+kind name are safe
(they will be ignored).
"""
e = Entry(**entry)
self._entries.append(e)
self._entry_map[e.kind][e.name] = e
self._entries_ordered.append(e)
def insert_clone(self, clone):
"""
Insert a clone into the metadata.
Args:
clone: A key-value dictionary describing a clone. Refer to
Clone#__init__ for the keys required/optional.
Remarks:
Subsequent calls to insert_clone with the same clone+kind name are safe
(they will be ignored). Also the target entry need not be inserted
ahead of the clone entry.
"""
entry_name = clone['name']
# figure out corresponding entry later. allow clone insert, entry insert
entry = None
c = Clone(entry, **clone)
self._entry_map[c.kind][c.name] = c
self._clones.append(c)
self._entries_ordered.append(c)
def prune_clones(self):
"""
Remove all clones that don't point to an existing entry.
Remarks:
This should be called after all insert_entry/insert_clone calls have
finished.
"""
remove_list = []
for p in self._clones:
if p.entry is None:
remove_list.append(p)
for p in remove_list:
# remove from parent's entries list
if p.parent is not None:
p.parent._entries.remove(p)
# remove from parents' _leafs list
for ancestor in p.find_parents(lambda x: not isinstance(x, MetadataSet)):
ancestor._leafs.remove(p)
# remove from global list
self._clones.remove(p)
self._entry_map[p.kind].pop(p.name)
self._entries_ordered.remove(p)
# After all entries/clones are inserted,
# invoke this to generate the parent/child node graph all these objects
def construct_graph(self):
"""
Generate the graph recursively, after which all Entry nodes will be
accessible recursively by crawling through the outer_namespaces sequence.
Remarks:
This is safe to be called multiple times at any time. It should be done at
least once or there will be no graph.
"""
self.validate_tree()
self._construct_tags()
self.validate_tree()
self._construct_clones()
self.validate_tree()
self._construct_outer_namespaces()
self.validate_tree()
def _construct_tags(self):
tag_dict = self._dictionary_by_name(self.tags)
for p in self._get_properties():
p._tags = []
for tag_id in p._tag_ids:
tag = tag_dict.get(tag_id)
if tag not in p._tags:
p._tags.append(tag)
if p not in tag.entries:
tag._entries.append(p)
def _construct_clones(self):
for p in self._clones:
target_kind = p.target_kind
target_entry = self._entry_map[target_kind].get(p.name)
p._entry = target_entry
# should not throw if we pass validation
# but can happen when importing obsolete CSV entries
if target_entry is None:
print >> sys.stderr, ("WARNING: Clone entry '%s' target kind '%s'" + \
" has no corresponding entry") \
%(p.name, p.target_kind)
def _construct_outer_namespaces(self):
if self._outer_namespaces is None: #the first time this runs
self._outer_namespaces = []
root = self._dictionary_by_name(self._outer_namespaces)
for ons_name, ons in root.iteritems():
ons._leafs = []
for p in self._entries_ordered:
ons_name = p.get_outer_namespace()
ons = root.get(ons_name, OuterNamespace(ons_name, self))
root[ons_name] = ons
if p not in ons._leafs:
ons._leafs.append(p)
for ons_name, ons in root.iteritems():
ons.validate_tree()
self._construct_sections(ons)
if ons not in self._outer_namespaces:
self._outer_namespaces.append(ons)
ons.validate_tree()
def _construct_sections(self, outer_namespace):
sections_dict = self._dictionary_by_name(outer_namespace.sections)
for sec_name, sec in sections_dict.iteritems():
sec._leafs = []
sec.validate_tree()
for p in outer_namespace._leafs:
does_exist = sections_dict.get(p.get_section())
sec = sections_dict.get(p.get_section(), \
Section(p.get_section(), outer_namespace))
sections_dict[p.get_section()] = sec
sec.validate_tree()
if p not in sec._leafs:
sec._leafs.append(p)
for sec_name, sec in sections_dict.iteritems():
if not sec.validate_tree():
print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
"construct_sections (start), with section = '%s'")\
%(sec)
self._construct_kinds(sec)
if sec not in outer_namespace.sections:
outer_namespace._sections.append(sec)
if not sec.validate_tree():
print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
"construct_sections (end), with section = '%s'") \
%(sec)
# 'controls', 'static' 'dynamic'. etc
def _construct_kinds(self, section):
for kind in section.kinds:
kind._leafs = []
section.validate_tree()
group_entry_by_kind = itertools.groupby(section._leafs, lambda x: x.kind)
leaf_it = ((k, g) for k, g in group_entry_by_kind)
# allow multiple kinds with the same name. merge if adjacent
# e.g. dynamic,dynamic,static,static,dynamic -> dynamic,static,dynamic
# this helps maintain ABI compatibility when adding an entry in a new kind
for idx, (kind_name, entry_it) in enumerate(leaf_it):
if idx >= len(section._kinds):
kind = Kind(kind_name, section)
section._kinds.append(kind)
section.validate_tree()
kind = section._kinds[idx]
for p in entry_it:
if p not in kind._leafs:
kind._leafs.append(p)
for kind in section._kinds:
kind.validate_tree()
self._construct_inner_namespaces(kind)
kind.validate_tree()
self._construct_entries(kind)
kind.validate_tree()
if not section.validate_tree():
print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
"construct_kinds, with kind = '%s'") %(kind)
if not kind.validate_tree():
print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
"construct_kinds, with kind = '%s'") %(kind)
def _construct_inner_namespaces(self, parent, depth=0):
#parent is InnerNamespace or Kind
ins_dict = self._dictionary_by_name(parent.namespaces)
for name, ins in ins_dict.iteritems():
ins._leafs = []
for p in parent._leafs:
ins_list = p.get_inner_namespace_list()
if len(ins_list) > depth:
ins_str = ins_list[depth]
ins = ins_dict.get(ins_str, InnerNamespace(ins_str, parent))
ins_dict[ins_str] = ins
if p not in ins._leafs:
ins._leafs.append(p)
for name, ins in ins_dict.iteritems():
ins.validate_tree()
# construct children INS
self._construct_inner_namespaces(ins, depth + 1)
ins.validate_tree()
# construct children entries
self._construct_entries(ins, depth + 1)
if ins not in parent.namespaces:
parent._namespaces.append(ins)
if not ins.validate_tree():
print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
"construct_inner_namespaces, with ins = '%s'") \
%(ins)
# doesnt construct the entries, so much as links them
def _construct_entries(self, parent, depth=0):
#parent is InnerNamespace or Kind
entry_dict = self._dictionary_by_name(parent.entries)
for p in parent._leafs:
ins_list = p.get_inner_namespace_list()
if len(ins_list) == depth:
entry = entry_dict.get(p.name, p)
entry_dict[p.name] = entry
for name, entry in entry_dict.iteritems():
old_parent = entry.parent
entry._parent = parent
if entry not in parent.entries:
parent._entries.append(entry)
if old_parent is not None and old_parent != parent:
print >> sys.stderr, ("ERROR: Parent changed from '%s' to '%s' for " + \
"entry '%s'") \
%(old_parent.name, parent.name, entry.name)
def _get_children(self):
if self.outer_namespaces is not None:
for i in self.outer_namespaces:
yield i
if self.tags is not None:
for i in self.tags:
yield i
class Tag(Node):
"""
A tag Node corresponding to a top-level <tag> element.
Attributes (Read-Only):
name: alias for id
id: The name of the tag, e.g. for <tag id="BC"/> id = 'BC'
description: The description of the tag, the contents of the <tag> element.
parent: An edge to the parent, which is always the Metadata root node.
entries: A sequence of edges to entries/clones that are using this Tag.
"""
def __init__(self, name, parent, description=""):
self._name = name # 'id' attribute in XML
self._id = name
self._description = description
self._parent = parent
# all entries that have this tag, including clones
self._entries = [] # filled in by Metadata#construct_tags
@property
def id(self):
return self._id
@property
def description(self):
return self._description
@property
def entries(self):
return (i for i in self._entries)
def _get_children(self):
return None
class OuterNamespace(Node):
"""
A node corresponding to a <namespace> element under <metadata>
Attributes (Read-Only):
name: The name attribute of the <namespace name="foo"> element.
parent: An edge to the parent, which is always the Metadata root node.
sections: A sequence of Section children.
"""
def __init__(self, name, parent, sections=[]):
self._name = name
self._parent = parent # MetadataSet
self._sections = sections[:]
self._leafs = []
self._children = self._sections
@property
def sections(self):
return (i for i in self._sections)
class Section(Node):
"""
A node corresponding to a <section> element under <namespace>
Attributes (Read-Only):
name: The name attribute of the <section name="foo"> element.
parent: An edge to the parent, which is always an OuterNamespace instance.
description: A string description of the section, or None.
kinds: A sequence of Kind children.
merged_kinds: A sequence of virtual Kind children,
with each Kind's children merged by the kind.name
"""
def __init__(self, name, parent, description=None, kinds=[]):
self._name = name
self._parent = parent
self._description = description
self._kinds = kinds[:]
self._leafs = []
@property
def description(self):
return self._description
@property
def kinds(self):
return (i for i in self._kinds)
def sort_children(self):
self.validate_tree()
# order is always controls,static,dynamic
find_child = lambda x: [i for i in self._get_children() if i.name == x]
new_lst = find_child('controls') \
+ find_child('static') \
+ find_child('dynamic')
self._kinds = new_lst
self.validate_tree()
def _get_children(self):
return (i for i in self.kinds)
@property
def merged_kinds(self):
def aggregate_by_name(acc, el):
existing = [i for i in acc if i.name == el.name]
if existing:
k = existing[0]
else:
k = Kind(el.name, el.parent)
acc.append(k)
k._namespaces.extend(el._namespaces)
k._entries.extend(el._entries)
return acc
new_kinds_lst = reduce(aggregate_by_name, self.kinds, [])
for k in new_kinds_lst:
yield k
class Kind(Node):
"""
A node corresponding to one of: <static>,<dynamic>,<controls> under a
<section> element.
Attributes (Read-Only):
name: A string which is one of 'static', 'dynamic, or 'controls'.
parent: An edge to the parent, which is always a Section instance.
namespaces: A sequence of InnerNamespace children.
entries: A sequence of Entry/Clone children.
merged_entries: A sequence of MergedEntry virtual nodes from entries
"""
def __init__(self, name, parent):
self._name = name
self._parent = parent
self._namespaces = []
self._entries = []
self._leafs = []
@property
def namespaces(self):
return self._namespaces
@property
def entries(self):
return self._entries
@property
def merged_entries(self):
for i in self.entries:
yield i.merge()
def sort_children(self):
self._namespaces.sort(key=self._get_name())
self._entries.sort(key=self._get_name())
def _get_children(self):
for i in self.namespaces:
yield i
for i in self.entries:
yield i
class InnerNamespace(Node):
"""
A node corresponding to a <namespace> which is an ancestor of a Kind.
These namespaces may have other namespaces recursively, or entries as leafs.
Attributes (Read-Only):
name: Name attribute from the element, e.g. <namespace name="foo"> -> 'foo'
parent: An edge to the parent, which is an InnerNamespace or a Kind.
namespaces: A sequence of InnerNamespace children.
entries: A sequence of Entry/Clone children.
merged_entries: A sequence of MergedEntry virtual nodes from entries
"""
def __init__(self, name, parent):
self._name = name
self._parent = parent
self._namespaces = []
self._entries = []
self._leafs = []
@property
def namespaces(self):
return self._namespaces
@property
def entries(self):
return self._entries
@property
def merged_entries(self):
for i in self.entries:
yield i.merge()
def sort_children(self):
self._namespaces.sort(key=self._get_name())
self._entries.sort(key=self._get_name())
def _get_children(self):
for i in self.namespaces:
yield i
for i in self.entries:
yield i
class EnumValue(Node):
"""
A class corresponding to a <value> element within an <enum> within an <entry>.
Attributes (Read-Only):
name: A string, e.g. 'ON' or 'OFF'
id: An optional numeric string, e.g. '0' or '0xFF'
optional: A boolean
notes: A string describing the notes, or None.
parent: An edge to the parent, always an Enum instance.
"""
def __init__(self, name, parent, id=None, optional=False, notes=None):
self._name = name # str, e.g. 'ON' or 'OFF'
self._id = id # int, e.g. '0'
self._optional = optional # bool
self._notes = notes # None or str
self._parent = parent
@property
def id(self):
return self._id
@property
def optional(self):
return self._optional
@property
def notes(self):
return self._notes
def _get_children(self):
return None
class Enum(Node):
"""
A class corresponding to an <enum> element within an <entry>.
Attributes (Read-Only):
parent: An edge to the parent, always an Entry instance.
values: A sequence of EnumValue children.
"""
def __init__(self, parent, values, ids={}, optionals=[], notes={}):
self._values = \
[ EnumValue(val, self, ids.get(val), val in optionals, notes.get(val)) \
for val in values ]
self._parent = parent
self._name = None
@property
def values(self):
return (i for i in self._values)
def _get_children(self):
return (i for i in self._values)
class Entry(Node):
"""
A node corresponding to an <entry> element.
Attributes (Read-Only):
parent: An edge to the parent node, which is an InnerNamespace or Kind.
name: The fully qualified name string, e.g. 'android.shading.mode'
name_short: The name attribute from <entry name="mode">, e.g. mode
type: The type attribute from <entry type="bar">
kind: A string ('static', 'dynamic', 'controls') corresponding to the
ancestor Kind#name
container: The container attribute from <entry container="array">, or None.
container_sizes: A sequence of size strings or None if container is None.
enum: An Enum instance if the enum attribute is true, None otherwise.
tuple_values: A sequence of strings describing the tuple values,
None if container is not 'tuple'.
description: A string description, or None.
range: A string range, or None.
units: A string units, or None.
tags: A sequence of Tag nodes associated with this Entry.
type_notes: A string describing notes for the type, or None.
Remarks:
Subclass Clone can be used interchangeable with an Entry,
for when we don't care about the underlying type.
parent and tags edges are invalid until after Metadata#construct_graph
has been invoked.
"""
def __init__(self, **kwargs):
"""
Instantiate a new Entry node.
Args:
name: A string with the fully qualified name, e.g. 'android.shading.mode'
type: A string describing the type, e.g. 'int32'
kind: A string describing the kind, e.g. 'static'
Args (if container):
container: A string describing the container, e.g. 'array' or 'tuple'
container_sizes: A list of string sizes if a container, or None otherwise
Args (if container is 'tuple'):
tuple_values: A list of tuple values, e.g. ['width', 'height']
Args (if the 'enum' attribute is true):
enum: A boolean, True if this is an enum, False otherwise
enum_values: A list of value strings, e.g. ['ON', 'OFF']
enum_optionals: A list of optional enum values, e.g. ['OFF']
enum_notes: A dictionary of value->notes strings.
enum_ids: A dictionary of value->id strings.
Args (optional):
description: A string with a description of the entry.
range: A string with the range of the values of the entry, e.g. '>= 0'
units: A string with the units of the values, e.g. 'inches'
notes: A string with the notes for the entry
tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
type_notes: A string with the notes for the type
"""
if kwargs.get('type') is None:
print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \
%(kwargs.get('name'), kwargs.get('kind'))
# Attributes are Read-Only, but edges may be mutated by
# Metadata, particularly during construct_graph
self._name = kwargs['name']
self._type = kwargs['type']
self._kind = kwargs['kind'] # static, dynamic, or controls
self._init_common(**kwargs)
@property
def type(self):
return self._type
@property
def kind(self):
return self._kind
@property
def name_short(self):
return self.get_name_minimal()
@property
def container(self):
return self._container
@property
def container_sizes(self):
if self._container_sizes is None:
return None
else:
return (i for i in self._container_sizes)
@property
def tuple_values(self):
if self._tuple_values is None:
return None
else:
return (i for i in self._tuple_values)
@property
def description(self):
return self._description
@property
def range(self):
return self._range
@property
def units(self):
return self._units
@property
def notes(self):
return self._notes
@property
def tags(self):
if self._tags is None:
return None
else:
return (i for i in self._tags)
@property
def type_notes(self):
return self._type_notes
@property
def enum(self):
return self._enum
def _get_children(self):
if self.enum:
yield self.enum
def sort_children(self):
return None
def is_clone(self):
"""
Whether or not this is a Clone instance.
Returns:
False
"""
return False
def _init_common(self, **kwargs):
self._parent = None # filled in by MetadataSet::_construct_entries
self._container = kwargs.get('container')
self._container_sizes = kwargs.get('container_sizes')
# access these via the 'enum' prop
enum_values = kwargs.get('enum_values')
enum_optionals = kwargs.get('enum_optionals')
enum_notes = kwargs.get('enum_notes') # { value => notes }
enum_ids = kwargs.get('enum_ids') # { value => notes }
self._tuple_values = kwargs.get('tuple_values')
self._description = kwargs.get('description')
self._range = kwargs.get('range')
self._units = kwargs.get('units')
self._notes = kwargs.get('notes')
self._tag_ids = kwargs.get('tag_ids', [])
self._tags = None # Filled in by MetadataSet::_construct_tags
self._type_notes = kwargs.get('type_notes')
if kwargs.get('enum', False):
self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes)
else:
self._enum = None
self._property_keys = kwargs
def merge(self):
"""
Copy the attributes into a new entry, merging it with the target entry
if it's a clone.
"""
return MergedEntry(self)
# Helpers for accessing less than the fully qualified name
def get_name_as_list(self):
"""
Returns the name as a list split by a period.
For example:
entry.name is 'android.lens.info.shading'
entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
"""
return self.name.split(".")
def get_inner_namespace_list(self):
"""
Returns the inner namespace part of the name as a list
For example:
entry.name is 'android.lens.info.shading'
entry.get_inner_namespace_list() == ['info']
"""
return self.get_name_as_list()[2:-1]
def get_outer_namespace(self):
"""
Returns the outer namespace as a string.
For example:
entry.name is 'android.lens.info.shading'
entry.get_outer_namespace() == 'android'
Remarks:
Since outer namespaces are non-recursive,
and each entry has one, this does not need to be a list.
"""
return self.get_name_as_list()[0]
def get_section(self):
"""
Returns the section as a string.
For example:
entry.name is 'android.lens.info.shading'
entry.get_section() == ''
Remarks:
Since outer namespaces are non-recursive,
and each entry has one, this does not need to be a list.
"""
return self.get_name_as_list()[1]
def get_name_minimal(self):
"""
Returns only the last component of the fully qualified name as a string.
For example:
entry.name is 'android.lens.info.shading'
entry.get_name_minimal() == 'shading'
Remarks:
entry.name_short it an alias for this
"""
return self.get_name_as_list()[-1]
def get_path_without_name(self):
"""
Returns a string path to the entry, with the name component excluded.
For example:
entry.name is 'android.lens.info.shading'
entry.get_path_without_name() == 'android.lens.info'
"""
return ".".join(self.get_name_as_list()[0:-1])
class Clone(Entry):
"""
A Node corresponding to a <clone> element. It has all the attributes of an
<entry> element (Entry) plus the additions specified below.
Attributes (Read-Only):
entry: an edge to an Entry object that this targets
target_kind: A string describing the kind of the target entry.
name: a string of the name, same as entry.name
kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
for the <clone> element.
type: always None, since a clone cannot override the type.
"""
def __init__(self, entry=None, **kwargs):
"""
Instantiate a new Clone node.
Args:
name: A string with the fully qualified name, e.g. 'android.shading.mode'
type: A string describing the type, e.g. 'int32'
kind: A string describing the kind, e.g. 'static'
target_kind: A string for the kind of the target entry, e.g. 'dynamic'
Args (if container):
container: A string describing the container, e.g. 'array' or 'tuple'
container_sizes: A list of string sizes if a container, or None otherwise
Args (if container is 'tuple'):
tuple_values: A list of tuple values, e.g. ['width', 'height']
Args (if the 'enum' attribute is true):
enum: A boolean, True if this is an enum, False otherwise
enum_values: A list of value strings, e.g. ['ON', 'OFF']
enum_optionals: A list of optional enum values, e.g. ['OFF']
enum_notes: A dictionary of value->notes strings.
enum_ids: A dictionary of value->id strings.
Args (optional):
entry: An edge to the corresponding target Entry.
description: A string with a description of the entry.
range: A string with the range of the values of the entry, e.g. '>= 0'
units: A string with the units of the values, e.g. 'inches'
notes: A string with the notes for the entry
tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
type_notes: A string with the notes for the type
Remarks:
Note that type is not specified since it has to be the same as the
entry.type.
"""
self._entry = entry # Entry object
self._target_kind = kwargs['target_kind']
self._name = kwargs['name'] # same as entry.name
self._kind = kwargs['kind']
# illegal to override the type, it should be the same as the entry
self._type = None
# the rest of the kwargs are optional
# can be used to override the regular entry data
self._init_common(**kwargs)
@property
def entry(self):
return self._entry
@property
def target_kind(self):
return self._target_kind
def is_clone(self):
"""
Whether or not this is a Clone instance.
Returns:
True
"""
return True
class MergedEntry(Entry):
"""
A MergedEntry has all the attributes of a Clone and its target Entry merged
together.
Remarks:
Useful when we want to 'unfold' a clone into a real entry by copying out
the target entry data. In this case we don't care about distinguishing
a clone vs an entry.
"""
def __init__(self, entry):
"""
Create a new instance of MergedEntry.
Args:
entry: An Entry or Clone instance
"""
props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind']
for p in props_distinct:
p = '_' + p
if entry.is_clone():
setattr(self, p, getattr(entry, p) or getattr(entry.entry, p))
else:
setattr(self, p, getattr(entry, p))
props_common = ['parent', 'name', 'container',
'container_sizes', 'enum',
'tuple_values',
'type',
'type_notes',
]
for p in props_common:
p = '_' + p
if entry.is_clone():
setattr(self, p, getattr(entry.entry, p))
else:
setattr(self, p, getattr(entry, p))