camera_metadata: Generate java metadata keys source code

Change-Id: Id1d1d4367eb51354e85c4eea38c593a498932e5b
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
+