camera_metadata: Generate java metadata keys source code
Change-Id: Id1d1d4367eb51354e85c4eea38c593a498932e5b
diff --git a/camera/docs/CameraMetadataKeys.mako b/camera/docs/CameraMetadataKeys.mako
new file mode 100644
index 0000000..18af316
--- /dev/null
+++ b/camera/docs/CameraMetadataKeys.mako
@@ -0,0 +1,123 @@
+## -*- coding: utf-8 -*-
+/*
+ * Copyright (C) 2013 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.
+ */
+
+package android.hardware.photography;
+
+import static android.hardware.photography.CameraMetadata.Key;
+
+/**
+ * ! Do not edit this file directly !
+ *
+ * Generated automatically from ${java_class}Keys.mako
+ *
+ * TODO: Include a hash of the input files here that the build can check.
+ */
+
+<%page args="java_class, xml_kind" />\
+/**
+ * The base class for camera controls and information.
+ *
+ * This class defines the basic key/value map used for querying for camera
+ * characteristics or capture results, and for setting camera request
+ * parameters.
+ *
+ * @see ${java_class}
+ * @see CameraMetadata
+ * @hide
+ **/
+##
+## Function to generate an enum
+<%def name="generate_enum(entry)">
+ public static final class ${entry.get_name_minimal() | pascal_case}Key extends Key<${jtype(entry)}> {
+ public enum Enum {
+ % for value,last in enumerate_with_last(entry.enum.values):
+ ${value.name | jidentifier}${"," if not last else ";"}
+ % endfor
+ }
+
+ % for value in entry.enum.values:
+ public static final Enum ${value.name | jidentifier} = Enum.${value.name | jidentifier};
+ % endfor
+
+ // TODO: remove requirement for constructor by making Key an interface
+ private ${entry.get_name_minimal() | pascal_case}Key(String name) {
+ super(name, ${jtype(entry)}.class);
+ }
+
+ % if entry.enum.has_values_with_id:
+ static {
+ CameraMetadata.registerEnumValues(${jenum(entry.enum)}.class, new int[] {
+ % for (value, last) in enumerate_with_last(entry.enum.values):
+ ${enum_calculate_value_string(value)}${"," if not last else ""} // ${value.name | jidentifier}
+ % endfor
+ });
+ }
+ % endif
+ }
+</%def>\
+##
+## Generate a list of only Static, Controls, or Dynamic properties.
+<%def name="single_kind_keys(java_name, xml_name)">\
+public final class ${java_name}Keys {
+% for outer_namespace in metadata.outer_namespaces: ## assumes single 'android' namespace
+ % for section in outer_namespace.sections:
+ % if section.find_first(lambda x: isinstance(x, metadata_model.Entry) and x.kind == xml_name):
+ public static final class ${section.name | pascal_case} {
+ % for inner_namespace in get_children_by_filtering_kind(section, xml_name, 'namespaces'):
+## We only support 1 level of inner namespace, i.e. android.a.b and android.a.b.c works, but not android.a.b.c.d
+## If we need to support more, we should use a recursive function here instead.. but the indentation gets trickier.
+ public static final class ${inner_namespace.name| pascal_case} {
+ % for entry in inner_namespace.merged_entries:
+ % if entry.enum:
+${generate_enum(entry)}
+ public static final Key<${jtype(entry)}> ${entry.get_name_minimal() | csym} =
+ new ${entry.get_name_minimal() | pascal_case}Key("${entry.name}");
+ % else:
+ public static final Key<${jtype(entry)}> ${entry.get_name_minimal() | csym} =
+ new Key<${jtype(entry)}>("${entry.name}", ${jclass(entry)});
+ % endif
+ % endfor
+ }
+ % endfor
+
+ % for entry in get_children_by_filtering_kind(section, xml_name, 'merged_entries'):
+ % if entry.enum:
+${generate_enum(entry)}
+ public static final Key<${jtype(entry)}> ${entry.get_name_minimal() | csym} =
+ new ${entry.get_name_minimal() | pascal_case}Key("${entry.name}");
+ % else:
+ public static final Key<${jtype(entry)}> ${entry.get_name_minimal() | csym} =
+ new Key<${jtype(entry)}>("${entry.name}", ${jclass(entry)});
+ % endif
+ % endfor
+
+ }
+ % endif
+ % endfor
+% endfor
+}
+</%def>\
+##
+## Static properties only
+##${single_kind_keys('CameraPropertiesKeys', 'static')}
+##
+## Controls properties only
+##${single_kind_keys('CaptureRequestKeys', 'controls')}
+##
+## Dynamic properties only
+##${single_kind_keys('CaptureResultKeys', 'dynamic')}
+${single_kind_keys(java_class, xml_kind)}
diff --git a/camera/docs/CameraPropertiesKeys.mako b/camera/docs/CameraPropertiesKeys.mako
new file mode 100644
index 0000000..ee10f45
--- /dev/null
+++ b/camera/docs/CameraPropertiesKeys.mako
@@ -0,0 +1,17 @@
+## -*- coding: utf-8 -*-
+##
+## Copyright (C) 2013 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.
+##
+<%include file="CameraMetadataKeys.mako" args="java_class='CameraProperties', xml_kind='static'" />
diff --git a/camera/docs/CaptureRequestKeys.mako b/camera/docs/CaptureRequestKeys.mako
new file mode 100644
index 0000000..bb8910f
--- /dev/null
+++ b/camera/docs/CaptureRequestKeys.mako
@@ -0,0 +1,17 @@
+## -*- coding: utf-8 -*-
+##
+## Copyright (C) 2013 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.
+##
+<%include file="CameraMetadataKeys.mako" args="java_class='CaptureRequest', xml_kind='controls'" />
diff --git a/camera/docs/CaptureResultKeys.mako b/camera/docs/CaptureResultKeys.mako
new file mode 100644
index 0000000..07bb139
--- /dev/null
+++ b/camera/docs/CaptureResultKeys.mako
@@ -0,0 +1,17 @@
+## -*- coding: utf-8 -*-
+##
+## Copyright (C) 2013 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.
+##
+<%include file="CameraMetadataKeys.mako" args="java_class='CaptureResult', xml_kind='dynamic'" />
diff --git a/camera/docs/metadata-generate b/camera/docs/metadata-generate
index 6084531..9b3a009 100755
--- a/camera/docs/metadata-generate
+++ b/camera/docs/metadata-generate
@@ -23,7 +23,13 @@
# ../src/camera_metadata_tags.h
#
+if [[ -z $ANDROID_BUILD_TOP ]]; then
+ echo "Please source build/envsetup.sh before running script" >& 2
+ exit 1
+fi
+
thisdir=$(cd "$(dirname "$0")"; pwd)
+fwkdir="$ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/photography/"
out_files=()
function relpath() {
@@ -35,6 +41,14 @@
local in=$thisdir/$1
local out=$thisdir/$2
+ gen_file_abs "$in" "$out"
+ return $?
+}
+
+function gen_file_abs() {
+ local in="$1"
+ local out="$2"
+
python $thisdir/metadata_parser_xml.py $thisdir/metadata_properties.xml $in $out
local succ=$?
@@ -67,7 +81,7 @@
echo "Diff result was $diff_result" >& /dev/null
echo "Diff result was $diff_result" >& /dev/null
if [[ $diff_result -eq 0 ]]; then
- echo "No changes in ${git_path}" >& /dev/null
+ echo "No changes in ${git_path}" >& /dev/null
else
echo "There are changes in ${git_path}" >& /dev/null
git_directories+=("$git_path")
@@ -76,7 +90,7 @@
done
# print as result the unique list of git directories affected
- printf %s\\n "${git_directories}" | sort | uniq
+ printf %s\\n "${git_directories[@]}" | sort | uniq
}
$thisdir/metadata-check-dependencies || exit 1
@@ -85,6 +99,9 @@
gen_file html.mako docs.html || exit 1
gen_file camera_metadata_tag_info.mako ../src/camera_metadata_tag_info.c || exit 1
gen_file camera_metadata_tags.mako ../include/system/camera_metadata_tags.h || exit 1
+gen_file_abs CaptureResultKeys.mako "$fwkdir/CaptureResultKeys.java" || exit 1
+gen_file_abs CaptureRequestKeys.mako "$fwkdir/CaptureRequestKeys.java" || exit 1
+gen_file_abs CameraPropertiesKeys.mako "$fwkdir/CameraPropertiesKeys.java" || exit 1
echo ""
echo "===================================================="
diff --git a/camera/docs/metadata_helpers.py b/camera/docs/metadata_helpers.py
index fd09d98..7d109e9 100644
--- a/camera/docs/metadata_helpers.py
+++ b/camera/docs/metadata_helpers.py
@@ -19,6 +19,7 @@
"""
import metadata_model
+import re
from collections import OrderedDict
_context_buf = None
@@ -113,6 +114,66 @@
return ".".join((i.name for i in path))
+def has_descendants_with_enums(node):
+ """
+ Determine whether or not the current node is or has any descendants with an
+ Enum node.
+
+ Args:
+ node: a Node instance
+
+ Returns:
+ True if it finds an Enum node in the subtree, False otherwise
+ """
+ return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum)))
+
+def get_children_by_throwing_away_kind(node, member='entries'):
+ """
+ Get the children of this node by compressing the subtree together by removing
+ the kind and then combining any children nodes with the same name together.
+
+ Args:
+ node: An instance of Section, InnerNamespace, or Kind
+
+ Returns:
+ An iterable over the combined children of the subtree of node,
+ as if the Kinds never existed.
+
+ Remarks:
+ Not recursive. Call this function repeatedly on each child.
+ """
+
+ if isinstance(node, metadata_model.Section):
+ # Note that this makes jump from Section to Kind,
+ # skipping the Kind entirely in the tree.
+ node_to_combine = node.combine_kinds_into_single_node()
+ else:
+ node_to_combine = node
+
+ combined_kind = node_to_combine.combine_children_by_name()
+
+ return (i for i in getattr(combined_kind, member))
+
+def get_children_by_filtering_kind(section, kind_name, member='entries'):
+ """
+ Takes a section and yields the children of the kind under this section.
+
+ Args:
+ section: An instance of Section
+ kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
+
+ Returns:
+ An iterable over the children of the specified kind.
+ """
+
+# TODO: test/use this function
+ matched_kind = next((i for i in section.kinds if i.name == kind_name), None)
+
+ if matched_kind:
+ return getattr(matched_kind, member)
+ else:
+ return ()
+
##
## Filters
##
@@ -241,3 +302,233 @@
code doesn't support enums directly yet.
"""
return 'TYPE_%s' %(what.upper())
+
+def jtype(entry):
+ """
+ Calculate the Java type from an entry type string, to be used as a generic
+ type argument in Java. The type is guaranteed to inherit from Object.
+
+ Remarks:
+ Since Java generics cannot be instantiated with primitives, this version
+ will use boxed types when absolutely required.
+
+ Returns:
+ The string representing the Java type.
+ """
+
+ if not isinstance(entry, metadata_model.Entry):
+ raise ValueError("Expected entry to be an instance of Entry")
+
+ primitive_type = entry.type
+
+ if entry.enum:
+ name = entry.name
+
+ name_without_ons = entry.get_name_as_list()[1:]
+ base_type = ".".join([pascal_case(i) for i in name_without_ons]) + \
+ "Key.Enum"
+ else:
+ mapping = {
+ 'int32': 'Integer',
+ 'int64': 'Long',
+ 'float': 'Float',
+ 'double': 'Double',
+ 'byte': 'Byte',
+ 'rational': 'Rational'
+ }
+
+ base_type = mapping[primitive_type]
+
+ if entry.container == 'array':
+ additional = '[]'
+
+ #unbox if it makes sense
+ if primitive_type != 'rational' and not entry.enum:
+ base_type = jtype_primitive(primitive_type)
+ else:
+ additional = ''
+
+ return "%s%s" %(base_type, additional)
+
+def jtype_primitive(what):
+ """
+ Calculate the Java type from an entry type string.
+
+ Remarks:
+ Makes a special exception for Rational, since it's a primitive in terms of
+ the C-library camera_metadata type system.
+
+ Returns:
+ The string representing the primitive type
+ """
+ mapping = {
+ 'int32': 'int',
+ 'int64': 'long',
+ 'float': 'float',
+ 'double': 'double',
+ 'byte': 'byte',
+ 'rational': 'Rational'
+ }
+
+ try:
+ return mapping[what]
+ except KeyError as e:
+ raise ValueError("Can't map '%s' to a primitive, not supported" %what)
+
+def jclass(entry):
+ """
+ Calculate the java Class reference string for an entry.
+
+ Args:
+ entry: an Entry node
+
+ Example:
+ <entry name="some_int" type="int32"/>
+ <entry name="some_int_array" type="int32" container='array'/>
+
+ jclass(some_int) == 'int.class'
+ jclass(some_int_array) == 'int[].class'
+
+ Returns:
+ The ClassName.class string
+ """
+ the_type = entry.type
+ try:
+ class_name = jtype_primitive(the_type)
+ except ValueError as e:
+ class_name = the_type
+
+ if entry.container == 'array':
+ class_name += "[]"
+
+ return "%s.class" %class_name
+
+def jidentifier(what):
+ """
+ Convert the input string into a valid Java identifier.
+
+ Args:
+ what: any identifier string
+
+ Returns:
+ String with added underscores if necessary.
+ """
+ if re.match("\d", what):
+ return "_%s" %what
+ else:
+ return what
+
+def enum_calculate_value_string(enum_value):
+ """
+ Calculate the value of the enum, even if it does not have one explicitly
+ defined.
+
+ This looks back for the first enum value that has a predefined value and then
+ applies addition until we get the right value, using C-enum semantics.
+
+ Args:
+ enum_value: an EnumValue node with a valid Enum parent
+
+ Example:
+ <enum>
+ <value>X</value>
+ <value id="5">Y</value>
+ <value>Z</value>
+ </enum>
+
+ enum_calculate_value_string(X) == '0'
+ enum_calculate_Value_string(Y) == '5'
+ enum_calculate_value_string(Z) == '6'
+
+ Returns:
+ String that represents the enum value as an integer literal.
+ """
+
+ enum_value_siblings = list(enum_value.parent.values)
+ this_index = enum_value_siblings.index(enum_value)
+
+ def is_hex_string(instr):
+ return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE))
+
+ base_value = 0
+ base_offset = 0
+ emit_as_hex = False
+
+ this_id = enum_value_siblings[this_index].id
+ while this_index != 0 and not this_id:
+ this_index -= 1
+ base_offset += 1
+ this_id = enum_value_siblings[this_index].id
+
+ if this_id:
+ base_value = int(this_id, 0) # guess base
+ emit_as_hex = is_hex_string(this_id)
+
+ if emit_as_hex:
+ return "0x%X" %(base_value + base_offset)
+ else:
+ return "%d" %(base_value + base_offset)
+
+def enumerate_with_last(iterable):
+ """
+ Enumerate a sequence of iterable, while knowing if this element is the last in
+ the sequence or not.
+
+ Args:
+ iterable: an Iterable of some sequence
+
+ Yields:
+ (element, bool) where the bool is True iff the element is last in the seq.
+ """
+ it = (i for i in iterable)
+
+ first = next(it) # OK: raises exception if it is empty
+
+ second = first # for when we have only 1 element in iterable
+
+ try:
+ while True:
+ second = next(it)
+ # more elements remaining.
+ yield (first, False)
+ first = second
+ except StopIteration:
+ # last element. no more elements left
+ yield (second, True)
+
+def pascal_case(what):
+ """
+ Convert the first letter of a string to uppercase, to make the identifier
+ conform to PascalCase.
+
+ Args:
+ what: a string representing some identifier
+
+ Returns:
+ String with first letter capitalized
+
+ Example:
+ pascal_case("helloWorld") == "HelloWorld"
+ pascal_case("foo") == "Foo"
+ """
+ return what[0:1].upper() + what[1:]
+
+def jenum(enum):
+ """
+ Calculate the Java symbol referencing an enum value (in Java).
+
+ Args:
+ enum: An Enum node
+
+ Returns:
+ String representing the Java symbol
+ """
+
+ entry = enum.parent
+ name = entry.name
+
+ name_without_ons = entry.get_name_as_list()[1:]
+ jenum_name = ".".join([pascal_case(i) for i in name_without_ons]) + "Key.Enum"
+
+ return jenum_name
+
diff --git a/camera/docs/metadata_helpers_test.py b/camera/docs/metadata_helpers_test.py
new file mode 100644
index 0000000..f4335cc
--- /dev/null
+++ b/camera/docs/metadata_helpers_test.py
@@ -0,0 +1,60 @@
+import unittest
+from unittest import TestCase
+from metadata_model import *
+from metadata_helpers import *
+
+class TestHelpers(TestCase):
+
+ def test_enum_calculate_value_string(self):
+ def compare_values_against_list(expected_list, enum):
+ for (idx, val) in enumerate(expected_list):
+ self.assertEquals(val,
+ enum_calculate_value_string(list(enum.values)[idx]))
+
+ plain_enum = Enum(parent=None, values=['ON', 'OFF'])
+
+ compare_values_against_list(['0', '1'],
+ plain_enum)
+
+ ###
+ labeled_enum = Enum(parent=None, values=['A', 'B', 'C'], ids={
+ 'A': '12345',
+ 'B': '0xC0FFEE',
+ 'C': '0xDEADF00D'
+ })
+
+ compare_values_against_list(['12345', '0xC0FFEE', '0xDEADF00D'],
+ labeled_enum)
+
+ ###
+ mixed_enum = Enum(parent=None,
+ values=['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
+ ids={
+ 'C': '0xC0FFEE',
+ 'E': '123',
+ 'G': '0xDEADF00D'
+ })
+
+ expected_values = ['0', '1', '0xC0FFEE', '0xC0FFEF', '123', '124',
+ '0xDEADF00D',
+ '0xDEADF00E']
+
+ compare_values_against_list(expected_values, mixed_enum)
+
+ def test_enumerate_with_last(self):
+ empty_list = []
+
+ for (x, y) in enumerate_with_last(empty_list):
+ self.fail("Should not return anything for empty list")
+
+ single_value = [1]
+ for (x, last) in enumerate_with_last(single_value):
+ self.assertEquals(1, x)
+ self.assertEquals(True, last)
+
+ multiple_values = [4, 5, 6]
+ lst = list(enumerate_with_last(multiple_values))
+ self.assertListEqual([(4, False), (5, False), (6, True)], lst)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/camera/docs/metadata_model.py b/camera/docs/metadata_model.py
index fa85a58..daf171c 100644
--- a/camera/docs/metadata_model.py
+++ b/camera/docs/metadata_model.py
@@ -23,12 +23,13 @@
Node: Base class for most nodes.
Entry: A node corresponding to <entry> elements.
Clone: A node corresponding to <clone> elements.
+ MergedEntry: A node corresponding to either <entry> or <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
+ EnumValue: 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.
"""
@@ -80,7 +81,6 @@
for j in i.find_all(pred):
yield j
-
def find_first(self, pred):
"""
Find the first descendant that matches the predicate.
@@ -147,7 +147,7 @@
def _children_name_map_matching(self, match=lambda x: True):
d = {}
- for i in _get_children():
+ for i in self._get_children():
if match(i):
d[i.name] = i
return d
@@ -283,7 +283,6 @@
(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)
@@ -310,7 +309,7 @@
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)):
+ for ancestor in p.find_parents(lambda x: not isinstance(x, Metadata)):
ancestor._leafs.remove(p)
# remove from global list
@@ -652,6 +651,37 @@
for k in new_kinds_lst:
yield k
+ def combine_kinds_into_single_node(self):
+ r"""
+ Combines the section's Kinds into a single node.
+
+ Combines all the children (kinds) of this section into a single
+ virtual Kind node.
+
+ Returns:
+ A new Kind node that collapses all Kind siblings into one, combining
+ all their children together.
+
+ For example, given self.kinds == [ x, y ]
+
+ x y z
+ / | | \ --> / | | \
+ a b c d a b c d
+
+ a new instance z is returned in this example.
+
+ Remarks:
+ The children of the kinds are the same references as before, that is
+ their parents will point to the old parents and not to the new parent.
+ """
+ combined = Kind(name="combined", parent=self)
+
+ for k in self._get_children():
+ combined._namespaces.extend(k.namespaces)
+ combined._entries.extend(k.entries)
+
+ return combined
+
class Kind(Node):
"""
A node corresponding to one of: <static>,<dynamic>,<controls> under a
@@ -695,6 +725,63 @@
for i in self.entries:
yield i
+ def combine_children_by_name(self):
+ r"""
+ Combine multiple children with the same name into a single node.
+
+ Returns:
+ A new Kind where all of the children with the same name were combined.
+
+ For example:
+
+ Given a Kind k:
+
+ k
+ / | \
+ a b c
+ | | |
+ d e f
+
+ a.name == "foo"
+ b.name == "foo"
+ c.name == "bar"
+
+ The returned Kind will look like this:
+
+ k'
+ / \
+ a' c'
+ / | |
+ d e f
+
+ Remarks:
+ This operation is not recursive. To combine the grandchildren and other
+ ancestors, call this method on the ancestor nodes.
+ """
+ return Kind._combine_children_by_name(self, new_type=type(self))
+
+ # new_type is either Kind or InnerNamespace
+ @staticmethod
+ def _combine_children_by_name(self, new_type):
+ new_ins_dict = OrderedDict()
+ new_ent_dict = OrderedDict()
+
+ for ins in self.namespaces:
+ new_ins = new_ins_dict.setdefault(ins.name,
+ InnerNamespace(ins.name, parent=self))
+ new_ins._namespaces.extend(ins.namespaces)
+ new_ins._entries.extend(ins.entries)
+
+ for ent in self.entries:
+ new_ent = new_ent_dict.setdefault(ent.name,
+ ent.merge())
+
+ kind = new_type(self.name, self.parent)
+ kind._namespaces = new_ins_dict.values()
+ kind._entries = new_ent_dict.values()
+
+ return kind
+
class InnerNamespace(Node):
"""
A node corresponding to a <namespace> which is an ancestor of a Kind.
@@ -737,6 +824,42 @@
for i in self.entries:
yield i
+ def combine_children_by_name(self):
+ r"""
+ Combine multiple children with the same name into a single node.
+
+ Returns:
+ A new InnerNamespace where all of the children with the same name were
+ combined.
+
+ For example:
+
+ Given an InnerNamespace i:
+
+ i
+ / | \
+ a b c
+ | | |
+ d e f
+
+ a.name == "foo"
+ b.name == "foo"
+ c.name == "bar"
+
+ The returned InnerNamespace will look like this:
+
+ i'
+ / \
+ a' c'
+ / | |
+ d e f
+
+ Remarks:
+ This operation is not recursive. To combine the grandchildren and other
+ ancestors, call this method on the ancestor nodes.
+ """
+ return Kind._combine_children_by_name(self, new_type=type(self))
+
class EnumValue(Node):
"""
A class corresponding to a <value> element within an <enum> within an <entry>.
@@ -777,6 +900,8 @@
Attributes (Read-Only):
parent: An edge to the parent, always an Entry instance.
values: A sequence of EnumValue children.
+ has_values_with_id: A boolean representing if any of the children have a
+ non-empty id property.
"""
def __init__(self, parent, values, ids={}, optionals=[], notes={}):
self._values = \
@@ -790,6 +915,10 @@
def values(self):
return (i for i in self._values)
+ @property
+ def has_values_with_id(self):
+ return bool(any(i for i in self.values if i.id))
+
def _get_children(self):
return (i for i in self._values)
@@ -954,8 +1083,8 @@
# 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 }
+ 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')
@@ -964,7 +1093,7 @@
self._notes = kwargs.get('notes')
self._tag_ids = kwargs.get('tag_ids', [])
- self._tags = None # Filled in by MetadataSet::_construct_tags
+ self._tags = None # Filled in by MetadataSet::_construct_tags
self._type_notes = kwargs.get('type_notes')
@@ -1106,9 +1235,9 @@
Note that type is not specified since it has to be the same as the
entry.type.
"""
- self._entry = entry # Entry object
+ self._entry = entry # Entry object
self._target_kind = kwargs['target_kind']
- self._name = kwargs['name'] # same as entry.name
+ 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
diff --git a/camera/docs/metadata_model_test.py b/camera/docs/metadata_model_test.py
new file mode 100644
index 0000000..eb79c9b
--- /dev/null
+++ b/camera/docs/metadata_model_test.py
@@ -0,0 +1,130 @@
+import unittest
+from unittest import TestCase
+from metadata_model import *
+
+class TestInnerNamespace(TestCase):
+ def test_combine_children_by_name(self):
+ #
+ # Set up
+ #
+ kind = Kind("some_root_kind", parent=None)
+ ins_outer = InnerNamespace("static", parent=kind)
+ kind._namespaces = [ins_outer]
+
+ ins1 = InnerNamespace("ins1", parent=ins_outer)
+ ins1a = InnerNamespace("ins1", parent=ins_outer) # same name deliberately
+ entry1 = Entry(name="entry1", type="int32", kind="static",
+ parent=ins1)
+ entry2 = Entry(name="entry2", type="int32", kind="static",
+ parent=ins1a)
+ entry3 = Entry(name="entry3", type="int32", kind="static",
+ parent=ins_outer)
+
+ ins_outer._namespaces = [ins1, ins1a]
+ ins_outer._entries = [entry3]
+
+ ins1._entries = [entry1]
+ ins1a._entries = [entry2]
+
+ #
+ # Test
+ #
+ combined_children_namespace = ins_outer.combine_children_by_name()
+
+ self.assertIsInstance(combined_children_namespace, InnerNamespace)
+ combined_ins = [i for i in combined_children_namespace.namespaces]
+ combined_ent = [i for i in combined_children_namespace.entries]
+
+ self.assertEquals(kind, combined_children_namespace.parent)
+ self.assertEquals(1, len(combined_ins))
+ self.assertEquals(1, len(combined_ent))
+
+ self.assertEquals("ins1", combined_ins[0].name)
+ self.assertEquals("entry3", combined_ent[0].name)
+
+ new_ins = combined_ins[0]
+ self.assertIn(entry1, new_ins.entries)
+ self.assertIn(entry2, new_ins.entries)
+
+
+class TestKind(TestCase):
+ def test_combine_kinds_into_single_node(self):
+ #
+ # Set up
+ #
+ section = Section("some_section", parent=None)
+ kind_static = Kind("static", parent=section)
+ kind_dynamic = Kind("dynamic", parent=section)
+ section._kinds = [kind_static, kind_dynamic]
+
+ ins1 = InnerNamespace("ins1", parent=kind_static)
+ ins2 = InnerNamespace("ins2", parent=kind_dynamic)
+ entry1 = Entry(name="entry1", type="int32", kind="static",
+ parent=kind_static)
+ entry2 = Entry(name="entry2", type="int32", kind="static",
+ parent=kind_dynamic)
+
+ kind_static._namespaces = [ins1]
+ kind_static._entries = [entry1]
+
+ kind_dynamic._namespaces = [ins2]
+ kind_dynamic._entries = [entry2]
+
+ #
+ # Test
+ #
+ combined_kind = section.combine_kinds_into_single_node()
+
+ self.assertEquals(section, combined_kind.parent)
+
+ self.assertIn(ins1, combined_kind.namespaces)
+ self.assertIn(ins2, combined_kind.namespaces)
+
+ self.assertIn(entry1, combined_kind.entries)
+ self.assertIn(entry2, combined_kind.entries)
+
+ def test_combine_children_by_name(self):
+ #
+ # Set up
+ #
+ section = Section("some_section", parent=None)
+ kind_static = Kind("static", parent=section)
+ section._kinds = [kind_static]
+
+ ins1 = InnerNamespace("ins1", parent=kind_static)
+ ins1a = InnerNamespace("ins1", parent=kind_static) # same name deliberately
+ entry1 = Entry(name="entry1", type="int32", kind="static",
+ parent=ins1)
+ entry2 = Entry(name="entry2", type="int32", kind="static",
+ parent=ins1a)
+ entry3 = Entry(name="entry3", type="int32", kind="static",
+ parent=kind_static)
+
+ kind_static._namespaces = [ins1, ins1a]
+ kind_static._entries = [entry3]
+
+ ins1._entries = [entry1]
+ ins1a._entries = [entry2]
+
+ #
+ # Test
+ #
+ combined_children_kind = kind_static.combine_children_by_name()
+
+ self.assertIsInstance(combined_children_kind, Kind)
+ combined_ins = [i for i in combined_children_kind.namespaces]
+ combined_ent = [i for i in combined_children_kind.entries]
+
+ self.assertEquals(section, combined_children_kind.parent)
+ self.assertEquals(1, len(combined_ins))
+ self.assertEquals(1, len(combined_ent))
+
+ self.assertEquals("ins1", combined_ins[0].name)
+ self.assertEquals("entry3", combined_ent[0].name)
+
+ new_ins = combined_ins[0]
+ self.assertIn(entry1, new_ins.entries)
+ self.assertIn(entry2, new_ins.entries)
+
+if __name__ == '__main__':
+ unittest.main()