1#
2# Copyright (C) 2012 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18A set of helpers for rendering Mako templates with a Metadata model.
19"""
20
21import metadata_model
22import re
23import markdown
24import textwrap
25import sys
26import bs4
27# Monkey-patch BS4. WBR element must not have an end tag.
28bs4.builder.HTMLTreeBuilder.empty_element_tags.add("wbr")
29
30from collections import OrderedDict
31
32# Relative path from HTML file to the base directory used by <img> tags
33IMAGE_SRC_METADATA="images/camera2/metadata/"
34
35# Prepend this path to each <img src="foo"> in javadocs
36JAVADOC_IMAGE_SRC_METADATA="/reference/" + IMAGE_SRC_METADATA
37NDKDOC_IMAGE_SRC_METADATA="../" + IMAGE_SRC_METADATA
38
39_context_buf = None
40_hal_major_version = None
41_hal_minor_version = None
42
43def _is_sec_or_ins(x):
44  return isinstance(x, metadata_model.Section) or    \
45         isinstance(x, metadata_model.InnerNamespace)
46
47##
48## Metadata Helpers
49##
50
51def find_all_sections(root):
52  """
53  Find all descendants that are Section or InnerNamespace instances.
54
55  Args:
56    root: a Metadata instance
57
58  Returns:
59    A list of Section/InnerNamespace instances
60
61  Remarks:
62    These are known as "sections" in the generated C code.
63  """
64  return root.find_all(_is_sec_or_ins)
65
66def find_parent_section(entry):
67  """
68  Find the closest ancestor that is either a Section or InnerNamespace.
69
70  Args:
71    entry: an Entry or Clone node
72
73  Returns:
74    An instance of Section or InnerNamespace
75  """
76  return entry.find_parent_first(_is_sec_or_ins)
77
78# find uniquely named entries (w/o recursing through inner namespaces)
79def find_unique_entries(node):
80  """
81  Find all uniquely named entries, without recursing through inner namespaces.
82
83  Args:
84    node: a Section or InnerNamespace instance
85
86  Yields:
87    A sequence of MergedEntry nodes representing an entry
88
89  Remarks:
90    This collapses multiple entries with the same fully qualified name into
91    one entry (e.g. if there are multiple entries in different kinds).
92  """
93  if not isinstance(node, metadata_model.Section) and    \
94     not isinstance(node, metadata_model.InnerNamespace):
95      raise TypeError("expected node to be a Section or InnerNamespace")
96
97  d = OrderedDict()
98  # remove the 'kinds' from the path between sec and the closest entries
99  # then search the immediate children of the search path
100  search_path = isinstance(node, metadata_model.Section) and node.kinds \
101                or [node]
102  for i in search_path:
103      for entry in i.entries:
104          d[entry.name] = entry
105
106  for k,v in d.iteritems():
107      yield v.merge()
108
109def path_name(node):
110  """
111  Calculate a period-separated string path from the root to this element,
112  by joining the names of each node and excluding the Metadata/Kind nodes
113  from the path.
114
115  Args:
116    node: a Node instance
117
118  Returns:
119    A string path
120  """
121
122  isa = lambda x,y: isinstance(x, y)
123  fltr = lambda x: not isa(x, metadata_model.Metadata) and \
124                   not isa(x, metadata_model.Kind)
125
126  path = node.find_parents(fltr)
127  path = list(path)
128  path.reverse()
129  path.append(node)
130
131  return ".".join((i.name for i in path))
132
133def ndk(name):
134  """
135  Return the NDK version of given name, which replace
136  the leading "android" to "acamera"
137
138  Args:
139    name: name string of an entry
140
141  Returns:
142    A NDK version name string of the input name
143  """
144  name_list = name.split(".")
145  if name_list[0] == "android":
146    name_list[0] = "acamera"
147  return ".".join(name_list)
148
149def protobuf_type(entry):
150  """
151  Return the protocol buffer message type for input metadata entry.
152  Only support types used by static metadata right now
153
154  Returns:
155    A string of protocol buffer type. Ex: "optional int32" or "repeated RangeInt"
156  """
157  typeName = None
158  if entry.typedef is None:
159    typeName = entry.type
160  else:
161    typeName = entry.typedef.name
162
163  typename_to_protobuftype = {
164    "rational"               : "Rational",
165    "size"                   : "Size",
166    "sizeF"                  : "SizeF",
167    "rectangle"              : "Rect",
168    "streamConfigurationMap" : "StreamConfigurations",
169    "rangeInt"               : "RangeInt",
170    "rangeLong"              : "RangeLong",
171    "colorSpaceTransform"    : "ColorSpaceTransform",
172    "blackLevelPattern"      : "BlackLevelPattern",
173    "byte"                   : "int32", # protocol buffer don't support byte
174    "boolean"                : "bool",
175    "float"                  : "float",
176    "double"                 : "double",
177    "int32"                  : "int32",
178    "int64"                  : "int64",
179    "enumList"               : "int32",
180    "string"                 : "string"
181  }
182
183  if typeName not in typename_to_protobuftype:
184    print >> sys.stderr,\
185      "  ERROR: Could not find protocol buffer type for {%s} type {%s} typedef {%s}" % \
186          (entry.name, entry.type, entry.typedef)
187
188  proto_type = typename_to_protobuftype[typeName]
189
190  prefix = "optional"
191  if entry.container == 'array':
192    has_variable_size = False
193    for size in entry.container_sizes:
194      try:
195        size_int = int(size)
196      except ValueError:
197        has_variable_size = True
198
199    if has_variable_size:
200      prefix = "repeated"
201
202  return "%s %s" %(prefix, proto_type)
203
204
205def protobuf_name(entry):
206  """
207  Return the protocol buffer field name for input metadata entry
208
209  Returns:
210    A string. Ex: "android_colorCorrection_availableAberrationModes"
211  """
212  return entry.name.replace(".", "_")
213
214def has_descendants_with_enums(node):
215  """
216  Determine whether or not the current node is or has any descendants with an
217  Enum node.
218
219  Args:
220    node: a Node instance
221
222  Returns:
223    True if it finds an Enum node in the subtree, False otherwise
224  """
225  return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum)))
226
227def get_children_by_throwing_away_kind(node, member='entries'):
228  """
229  Get the children of this node by compressing the subtree together by removing
230  the kind and then combining any children nodes with the same name together.
231
232  Args:
233    node: An instance of Section, InnerNamespace, or Kind
234
235  Returns:
236    An iterable over the combined children of the subtree of node,
237    as if the Kinds never existed.
238
239  Remarks:
240    Not recursive. Call this function repeatedly on each child.
241  """
242
243  if isinstance(node, metadata_model.Section):
244    # Note that this makes jump from Section to Kind,
245    # skipping the Kind entirely in the tree.
246    node_to_combine = node.combine_kinds_into_single_node()
247  else:
248    node_to_combine = node
249
250  combined_kind = node_to_combine.combine_children_by_name()
251
252  return (i for i in getattr(combined_kind, member))
253
254def get_children_by_filtering_kind(section, kind_name, member='entries'):
255  """
256  Takes a section and yields the children of the merged kind under this section.
257
258  Args:
259    section: An instance of Section
260    kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
261
262  Returns:
263    An iterable over the children of the specified merged kind.
264  """
265
266  matched_kind = next((i for i in section.merged_kinds if i.name == kind_name), None)
267
268  if matched_kind:
269    return getattr(matched_kind, member)
270  else:
271    return ()
272
273##
274## Filters
275##
276
277# abcDef.xyz -> ABC_DEF_XYZ
278def csym(name):
279  """
280  Convert an entry name string into an uppercase C symbol.
281
282  Returns:
283    A string
284
285  Example:
286    csym('abcDef.xyz') == 'ABC_DEF_XYZ'
287  """
288  newstr = name
289  newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper()
290  newstr = newstr.replace(".", "_")
291  return newstr
292
293# abcDef.xyz -> abc_def_xyz
294def csyml(name):
295  """
296  Convert an entry name string into a lowercase C symbol.
297
298  Returns:
299    A string
300
301  Example:
302    csyml('abcDef.xyz') == 'abc_def_xyz'
303  """
304  return csym(name).lower()
305
306# pad with spaces to make string len == size. add new line if too big
307def ljust(size, indent=4):
308  """
309  Creates a function that given a string will pad it with spaces to make
310  the string length == size. Adds a new line if the string was too big.
311
312  Args:
313    size: an integer representing how much spacing should be added
314    indent: an integer representing the initial indendation level
315
316  Returns:
317    A function that takes a string and returns a string.
318
319  Example:
320    ljust(8)("hello") == 'hello   '
321
322  Remarks:
323    Deprecated. Use pad instead since it works for non-first items in a
324    Mako template.
325  """
326  def inner(what):
327    newstr = what.ljust(size)
328    if len(newstr) > size:
329      return what + "\n" + "".ljust(indent + size)
330    else:
331      return newstr
332  return inner
333
334def _find_new_line():
335
336  if _context_buf is None:
337    raise ValueError("Context buffer was not set")
338
339  buf = _context_buf
340  x = -1 # since the first read is always ''
341  cur_pos = buf.tell()
342  while buf.tell() > 0 and buf.read(1) != '\n':
343    buf.seek(cur_pos - x)
344    x = x + 1
345
346  buf.seek(cur_pos)
347
348  return int(x)
349
350# Pad the string until the buffer reaches the desired column.
351# If string is too long, insert a new line with 'col' spaces instead
352def pad(col):
353  """
354  Create a function that given a string will pad it to the specified column col.
355  If the string overflows the column, put the string on a new line and pad it.
356
357  Args:
358    col: an integer specifying the column number
359
360  Returns:
361    A function that given a string will produce a padded string.
362
363  Example:
364    pad(8)("hello") == 'hello   '
365
366  Remarks:
367    This keeps track of the line written by Mako so far, so it will always
368    align to the column number correctly.
369  """
370  def inner(what):
371    wut = int(col)
372    current_col = _find_new_line()
373
374    if len(what) > wut - current_col:
375      return what + "\n".ljust(col)
376    else:
377      return what.ljust(wut - current_col)
378  return inner
379
380# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32
381def ctype_enum(what):
382  """
383  Generate a camera_metadata_type_t symbol from a type string.
384
385  Args:
386    what: a type string
387
388  Returns:
389    A string representing the camera_metadata_type_t
390
391  Example:
392    ctype_enum('int32') == 'TYPE_INT32'
393    ctype_enum('int64') == 'TYPE_INT64'
394    ctype_enum('float') == 'TYPE_FLOAT'
395
396  Remarks:
397    An enum is coerced to a byte since the rest of the camera_metadata
398    code doesn't support enums directly yet.
399  """
400  return 'TYPE_%s' %(what.upper())
401
402
403# Calculate a java type name from an entry with a Typedef node
404def _jtypedef_type(entry):
405  typedef = entry.typedef
406  additional = ''
407
408  # Hacky way to deal with arrays. Assume that if we have
409  # size 'Constant x N' the Constant is part of the Typedef size.
410  # So something sized just 'Constant', 'Constant1 x Constant2', etc
411  # is not treated as a real java array.
412  if entry.container == 'array':
413    has_variable_size = False
414    for size in entry.container_sizes:
415      try:
416        size_int = int(size)
417      except ValueError:
418        has_variable_size = True
419
420    if has_variable_size:
421      additional = '[]'
422
423  try:
424    name = typedef.languages['java']
425
426    return "%s%s" %(name, additional)
427  except KeyError:
428    return None
429
430# Box if primitive. Otherwise leave unboxed.
431def _jtype_box(type_name):
432  mapping = {
433    'boolean': 'Boolean',
434    'byte': 'Byte',
435    'int': 'Integer',
436    'float': 'Float',
437    'double': 'Double',
438    'long': 'Long'
439  }
440
441  return mapping.get(type_name, type_name)
442
443def jtype_unboxed(entry):
444  """
445  Calculate the Java type from an entry type string, to be used whenever we
446  need the regular type in Java. It's not boxed, so it can't be used as a
447  generic type argument when the entry type happens to resolve to a primitive.
448
449  Remarks:
450    Since Java generics cannot be instantiated with primitives, this version
451    is not applicable in that case. Use jtype_boxed instead for that.
452
453  Returns:
454    The string representing the Java type.
455  """
456  if not isinstance(entry, metadata_model.Entry):
457    raise ValueError("Expected entry to be an instance of Entry")
458
459  metadata_type = entry.type
460
461  java_type = None
462
463  if entry.typedef:
464    typedef_name = _jtypedef_type(entry)
465    if typedef_name:
466      java_type = typedef_name # already takes into account arrays
467
468  if not java_type:
469    if not java_type and entry.enum and metadata_type == 'byte':
470      # Always map byte enums to Java ints, unless there's a typedef override
471      base_type = 'int'
472
473    else:
474      mapping = {
475        'int32': 'int',
476        'int64': 'long',
477        'float': 'float',
478        'double': 'double',
479        'byte': 'byte',
480        'rational': 'Rational'
481      }
482
483      base_type = mapping[metadata_type]
484
485    # Convert to array (enums, basic types)
486    if entry.container == 'array':
487      additional = '[]'
488    else:
489      additional = ''
490
491    java_type = '%s%s' %(base_type, additional)
492
493  # Now box this sucker.
494  return java_type
495
496def jtype_boxed(entry):
497  """
498  Calculate the Java type from an entry type string, to be used as a generic
499  type argument in Java. The type is guaranteed to inherit from Object.
500
501  It will only box when absolutely necessary, i.e. int -> Integer[], but
502  int[] -> int[].
503
504  Remarks:
505    Since Java generics cannot be instantiated with primitives, this version
506    will use boxed types when absolutely required.
507
508  Returns:
509    The string representing the boxed Java type.
510  """
511  unboxed_type = jtype_unboxed(entry)
512  return _jtype_box(unboxed_type)
513
514def _is_jtype_generic(entry):
515  """
516  Determine whether or not the Java type represented by the entry type
517  string and/or typedef is a Java generic.
518
519  For example, "Range<Integer>" would be considered a generic, whereas
520  a "MeteringRectangle" or a plain "Integer" would not be considered a generic.
521
522  Args:
523    entry: An instance of an Entry node
524
525  Returns:
526    True if it's a java generic, False otherwise.
527  """
528  if entry.typedef:
529    local_typedef = _jtypedef_type(entry)
530    if local_typedef:
531      match = re.search(r'<.*>', local_typedef)
532      return bool(match)
533  return False
534
535def _jtype_primitive(what):
536  """
537  Calculate the Java type from an entry type string.
538
539  Remarks:
540    Makes a special exception for Rational, since it's a primitive in terms of
541    the C-library camera_metadata type system.
542
543  Returns:
544    The string representing the primitive type
545  """
546  mapping = {
547    'int32': 'int',
548    'int64': 'long',
549    'float': 'float',
550    'double': 'double',
551    'byte': 'byte',
552    'rational': 'Rational'
553  }
554
555  try:
556    return mapping[what]
557  except KeyError as e:
558    raise ValueError("Can't map '%s' to a primitive, not supported" %what)
559
560def jclass(entry):
561  """
562  Calculate the java Class reference string for an entry.
563
564  Args:
565    entry: an Entry node
566
567  Example:
568    <entry name="some_int" type="int32"/>
569    <entry name="some_int_array" type="int32" container='array'/>
570
571    jclass(some_int) == 'int.class'
572    jclass(some_int_array) == 'int[].class'
573
574  Returns:
575    The ClassName.class string
576  """
577
578  return "%s.class" %jtype_unboxed(entry)
579
580def jkey_type_token(entry):
581  """
582  Calculate the java type token compatible with a Key constructor.
583  This will be the Java Class<T> for non-generic classes, and a
584  TypeReference<T> for generic classes.
585
586  Args:
587    entry: An entry node
588
589  Returns:
590    The ClassName.class string, or 'new TypeReference<ClassName>() {{ }}' string
591  """
592  if _is_jtype_generic(entry):
593    return "new TypeReference<%s>() {{ }}" %(jtype_boxed(entry))
594  else:
595    return jclass(entry)
596
597def jidentifier(what):
598  """
599  Convert the input string into a valid Java identifier.
600
601  Args:
602    what: any identifier string
603
604  Returns:
605    String with added underscores if necessary.
606  """
607  if re.match("\d", what):
608    return "_%s" %what
609  else:
610    return what
611
612def enum_calculate_value_string(enum_value):
613  """
614  Calculate the value of the enum, even if it does not have one explicitly
615  defined.
616
617  This looks back for the first enum value that has a predefined value and then
618  applies addition until we get the right value, using C-enum semantics.
619
620  Args:
621    enum_value: an EnumValue node with a valid Enum parent
622
623  Example:
624    <enum>
625      <value>X</value>
626      <value id="5">Y</value>
627      <value>Z</value>
628    </enum>
629
630    enum_calculate_value_string(X) == '0'
631    enum_calculate_Value_string(Y) == '5'
632    enum_calculate_value_string(Z) == '6'
633
634  Returns:
635    String that represents the enum value as an integer literal.
636  """
637
638  enum_value_siblings = list(enum_value.parent.values)
639  this_index = enum_value_siblings.index(enum_value)
640
641  def is_hex_string(instr):
642    return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE))
643
644  base_value = 0
645  base_offset = 0
646  emit_as_hex = False
647
648  this_id = enum_value_siblings[this_index].id
649  while this_index != 0 and not this_id:
650    this_index -= 1
651    base_offset += 1
652    this_id = enum_value_siblings[this_index].id
653
654  if this_id:
655    base_value = int(this_id, 0)  # guess base
656    emit_as_hex = is_hex_string(this_id)
657
658  if emit_as_hex:
659    return "0x%X" %(base_value + base_offset)
660  else:
661    return "%d" %(base_value + base_offset)
662
663def enumerate_with_last(iterable):
664  """
665  Enumerate a sequence of iterable, while knowing if this element is the last in
666  the sequence or not.
667
668  Args:
669    iterable: an Iterable of some sequence
670
671  Yields:
672    (element, bool) where the bool is True iff the element is last in the seq.
673  """
674  it = (i for i in iterable)
675
676  first = next(it)  # OK: raises exception if it is empty
677
678  second = first  # for when we have only 1 element in iterable
679
680  try:
681    while True:
682      second = next(it)
683      # more elements remaining.
684      yield (first, False)
685      first = second
686  except StopIteration:
687    # last element. no more elements left
688    yield (second, True)
689
690def pascal_case(what):
691  """
692  Convert the first letter of a string to uppercase, to make the identifier
693  conform to PascalCase.
694
695  If there are dots, remove the dots, and capitalize the letter following
696  where the dot was. Letters that weren't following dots are left unchanged,
697  except for the first letter of the string (which is made upper-case).
698
699  Args:
700    what: a string representing some identifier
701
702  Returns:
703    String with first letter capitalized
704
705  Example:
706    pascal_case("helloWorld") == "HelloWorld"
707    pascal_case("foo") == "Foo"
708    pascal_case("hello.world") = "HelloWorld"
709    pascal_case("fooBar.fooBar") = "FooBarFooBar"
710  """
711  return "".join([s[0:1].upper() + s[1:] for s in what.split('.')])
712
713def jkey_identifier(what):
714  """
715  Return a Java identifier from a property name.
716
717  Args:
718    what: a string representing a property name.
719
720  Returns:
721    Java identifier corresponding to the property name. May need to be
722    prepended with the appropriate Java class name by the caller of this
723    function. Note that the outer namespace is stripped from the property
724    name.
725
726  Example:
727    jkey_identifier("android.lens.facing") == "LENS_FACING"
728  """
729  return csym(what[what.find('.') + 1:])
730
731def jenum_value(enum_entry, enum_value):
732  """
733  Calculate the Java name for an integer enum value
734
735  Args:
736    enum: An enum-typed Entry node
737    value: An EnumValue node for the enum
738
739  Returns:
740    String representing the Java symbol
741  """
742
743  cname = csym(enum_entry.name)
744  return cname[cname.find('_') + 1:] + '_' + enum_value.name
745
746def generate_extra_javadoc_detail(entry):
747  """
748  Returns a function to add extra details for an entry into a string for inclusion into
749  javadoc. Adds information about units, the list of enum values for this key, and the valid
750  range.
751  """
752  def inner(text):
753    if entry.units:
754      text += '\n\n<b>Units</b>: %s\n' % (dedent(entry.units))
755    if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')):
756      text += '\n\n<b>Possible values:</b>\n<ul>\n'
757      for value in entry.enum.values:
758        if not value.hidden:
759          text += '  <li>{@link #%s %s}</li>\n' % ( jenum_value(entry, value ), value.name )
760      text += '</ul>\n'
761    if entry.range:
762      if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')):
763        text += '\n\n<b>Available values for this device:</b><br>\n'
764      else:
765        text += '\n\n<b>Range of valid values:</b><br>\n'
766      text += '%s\n' % (dedent(entry.range))
767    if entry.hwlevel != 'legacy': # covers any of (None, 'limited', 'full')
768      text += '\n\n<b>Optional</b> - This value may be {@code null} on some devices.\n'
769    if entry.hwlevel == 'full':
770      text += \
771        '\n<b>Full capability</b> - \n' + \
772        'Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the\n' + \
773        'android.info.supportedHardwareLevel key\n'
774    if entry.hwlevel == 'limited':
775      text += \
776        '\n<b>Limited capability</b> - \n' + \
777        'Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the\n' + \
778        'android.info.supportedHardwareLevel key\n'
779    if entry.hwlevel == 'legacy':
780      text += "\nThis key is available on all devices."
781
782    return text
783  return inner
784
785
786def javadoc(metadata, indent = 4):
787  """
788  Returns a function to format a markdown syntax text block as a
789  javadoc comment section, given a set of metadata
790
791  Args:
792    metadata: A Metadata instance, representing the top-level root
793      of the metadata for cross-referencing
794    indent: baseline level of indentation for javadoc block
795  Returns:
796    A function that transforms a String text block as follows:
797    - Indent and * for insertion into a Javadoc comment block
798    - Trailing whitespace removed
799    - Entire body rendered via markdown to generate HTML
800    - All tag names converted to appropriate Javadoc {@link} with @see
801      for each tag
802
803  Example:
804    "This is a comment for Javadoc\n" +
805    "     with multiple lines, that should be   \n" +
806    "     formatted better\n" +
807    "\n" +
808    "    That covers multiple lines as well\n"
809    "    And references android.control.mode\n"
810
811    transforms to
812    "    * <p>This is a comment for Javadoc\n" +
813    "    * with multiple lines, that should be\n" +
814    "    * formatted better</p>\n" +
815    "    * <p>That covers multiple lines as well</p>\n" +
816    "    * and references {@link CaptureRequest#CONTROL_MODE android.control.mode}\n" +
817    "    *\n" +
818    "    * @see CaptureRequest#CONTROL_MODE\n"
819  """
820  def javadoc_formatter(text):
821    comment_prefix = " " * indent + " * "
822
823    # render with markdown => HTML
824    javatext = md(text, JAVADOC_IMAGE_SRC_METADATA)
825
826    # Identity transform for javadoc links
827    def javadoc_link_filter(target, target_ndk, shortname):
828      return '{@link %s %s}' % (target, shortname)
829
830    javatext = filter_links(javatext, javadoc_link_filter)
831
832    # Crossref tag names
833    kind_mapping = {
834        'static': 'CameraCharacteristics',
835        'dynamic': 'CaptureResult',
836        'controls': 'CaptureRequest' }
837
838    # Convert metadata entry "android.x.y.z" to form
839    # "{@link CaptureRequest#X_Y_Z android.x.y.z}"
840    def javadoc_crossref_filter(node):
841      if node.applied_visibility in ('public', 'java_public'):
842        return '{@link %s#%s %s}' % (kind_mapping[node.kind],
843                                     jkey_identifier(node.name),
844                                     node.name)
845      else:
846        return node.name
847
848    # For each public tag "android.x.y.z" referenced, add a
849    # "@see CaptureRequest#X_Y_Z"
850    def javadoc_crossref_see_filter(node_set):
851      node_set = (x for x in node_set if x.applied_visibility in ('public', 'java_public'))
852
853      text = '\n'
854      for node in node_set:
855        text = text + '\n@see %s#%s' % (kind_mapping[node.kind],
856                                      jkey_identifier(node.name))
857
858      return text if text != '\n' else ''
859
860    javatext = filter_tags(javatext, metadata, javadoc_crossref_filter, javadoc_crossref_see_filter)
861
862    def line_filter(line):
863      # Indent each line
864      # Add ' * ' to it for stylistic reasons
865      # Strip right side of trailing whitespace
866      return (comment_prefix + line).rstrip()
867
868    # Process each line with above filter
869    javatext = "\n".join(line_filter(i) for i in javatext.split("\n")) + "\n"
870
871    return javatext
872
873  return javadoc_formatter
874
875def ndkdoc(metadata, indent = 4):
876  """
877  Returns a function to format a markdown syntax text block as a
878  NDK camera API C/C++ comment section, given a set of metadata
879
880  Args:
881    metadata: A Metadata instance, representing the top-level root
882      of the metadata for cross-referencing
883    indent: baseline level of indentation for comment block
884  Returns:
885    A function that transforms a String text block as follows:
886    - Indent and * for insertion into a comment block
887    - Trailing whitespace removed
888    - Entire body rendered via markdown
889    - All tag names converted to appropriate NDK tag name for each tag
890
891  Example:
892    "This is a comment for NDK\n" +
893    "     with multiple lines, that should be   \n" +
894    "     formatted better\n" +
895    "\n" +
896    "    That covers multiple lines as well\n"
897    "    And references android.control.mode\n"
898
899    transforms to
900    "    * This is a comment for NDK\n" +
901    "    * with multiple lines, that should be\n" +
902    "    * formatted better\n" +
903    "    * That covers multiple lines as well\n" +
904    "    * and references ACAMERA_CONTROL_MODE\n" +
905    "    *\n" +
906    "    * @see ACAMERA_CONTROL_MODE\n"
907  """
908  def ndkdoc_formatter(text):
909    # render with markdown => HTML
910    # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags
911    ndktext = md(text, NDKDOC_IMAGE_SRC_METADATA, False)
912
913    # Simple transform for ndk doc links
914    def ndkdoc_link_filter(target, target_ndk, shortname):
915      if target_ndk is not None:
916        return '{@link %s %s}' % (target_ndk, shortname)
917
918      # Create HTML link to Javadoc
919      if shortname == '':
920        lastdot = target.rfind('.')
921        if lastdot == -1:
922          shortname = target
923        else:
924          shortname = target[lastdot + 1:]
925
926      target = target.replace('.','/')
927      if target.find('#') != -1:
928        target = target.replace('#','.html#')
929      else:
930        target = target + '.html'
931
932      return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname)
933
934    ndktext = filter_links(ndktext, ndkdoc_link_filter)
935
936    # Convert metadata entry "android.x.y.z" to form
937    # NDK tag format of "ACAMERA_X_Y_Z"
938    def ndkdoc_crossref_filter(node):
939      if node.applied_ndk_visible == 'true':
940        return csym(ndk(node.name))
941      else:
942        return node.name
943
944    # For each public tag "android.x.y.z" referenced, add a
945    # "@see ACAMERA_X_Y_Z"
946    def ndkdoc_crossref_see_filter(node_set):
947      node_set = (x for x in node_set if x.applied_ndk_visible == 'true')
948
949      text = '\n'
950      for node in node_set:
951        text = text + '\n@see %s' % (csym(ndk(node.name)))
952
953      return text if text != '\n' else ''
954
955    ndktext = filter_tags(ndktext, metadata, ndkdoc_crossref_filter, ndkdoc_crossref_see_filter)
956
957    ndktext = ndk_replace_tag_wildcards(ndktext, metadata)
958
959    comment_prefix = " " * indent + " * ";
960
961    def line_filter(line):
962      # Indent each line
963      # Add ' * ' to it for stylistic reasons
964      # Strip right side of trailing whitespace
965      return (comment_prefix + line).rstrip()
966
967    # Process each line with above filter
968    ndktext = "\n".join(line_filter(i) for i in ndktext.split("\n")) + "\n"
969
970    return ndktext
971
972  return ndkdoc_formatter
973
974def hidldoc(metadata, indent = 4):
975  """
976  Returns a function to format a markdown syntax text block as a
977  HIDL camera HAL module C/C++ comment section, given a set of metadata
978
979  Args:
980    metadata: A Metadata instance, representing the top-level root
981      of the metadata for cross-referencing
982    indent: baseline level of indentation for comment block
983  Returns:
984    A function that transforms a String text block as follows:
985    - Indent and * for insertion into a comment block
986    - Trailing whitespace removed
987    - Entire body rendered via markdown
988    - All tag names converted to appropriate HIDL tag name for each tag
989
990  Example:
991    "This is a comment for NDK\n" +
992    "     with multiple lines, that should be   \n" +
993    "     formatted better\n" +
994    "\n" +
995    "    That covers multiple lines as well\n"
996    "    And references android.control.mode\n"
997
998    transforms to
999    "    * This is a comment for NDK\n" +
1000    "    * with multiple lines, that should be\n" +
1001    "    * formatted better\n" +
1002    "    * That covers multiple lines as well\n" +
1003    "    * and references ANDROID_CONTROL_MODE\n" +
1004    "    *\n" +
1005    "    * @see ANDROID_CONTROL_MODE\n"
1006  """
1007  def hidldoc_formatter(text):
1008    # render with markdown => HTML
1009    # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags
1010    hidltext = md(text, NDKDOC_IMAGE_SRC_METADATA, False)
1011
1012    # Simple transform for hidl doc links
1013    def hidldoc_link_filter(target, target_ndk, shortname):
1014      if target_ndk is not None:
1015        return '{@link %s %s}' % (target_ndk, shortname)
1016
1017      # Create HTML link to Javadoc
1018      if shortname == '':
1019        lastdot = target.rfind('.')
1020        if lastdot == -1:
1021          shortname = target
1022        else:
1023          shortname = target[lastdot + 1:]
1024
1025      target = target.replace('.','/')
1026      if target.find('#') != -1:
1027        target = target.replace('#','.html#')
1028      else:
1029        target = target + '.html'
1030
1031      return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname)
1032
1033    hidltext = filter_links(hidltext, hidldoc_link_filter)
1034
1035    # Convert metadata entry "android.x.y.z" to form
1036    # HIDL tag format of "ANDROID_X_Y_Z"
1037    def hidldoc_crossref_filter(node):
1038      return csym(node.name)
1039
1040    # For each public tag "android.x.y.z" referenced, add a
1041    # "@see ANDROID_X_Y_Z"
1042    def hidldoc_crossref_see_filter(node_set):
1043      text = '\n'
1044      for node in node_set:
1045        text = text + '\n@see %s' % (csym(node.name))
1046
1047      return text if text != '\n' else ''
1048
1049    hidltext = filter_tags(hidltext, metadata, hidldoc_crossref_filter, hidldoc_crossref_see_filter)
1050
1051    comment_prefix = " " * indent + " * ";
1052
1053    def line_filter(line):
1054      # Indent each line
1055      # Add ' * ' to it for stylistic reasons
1056      # Strip right side of trailing whitespace
1057      return (comment_prefix + line).rstrip()
1058
1059    # Process each line with above filter
1060    hidltext = "\n".join(line_filter(i) for i in hidltext.split("\n")) + "\n"
1061
1062    return hidltext
1063
1064  return hidldoc_formatter
1065
1066def dedent(text):
1067  """
1068  Remove all common indentation from every line but the 0th.
1069  This will avoid getting <code> blocks when rendering text via markdown.
1070  Ignoring the 0th line will also allow the 0th line not to be aligned.
1071
1072  Args:
1073    text: A string of text to dedent.
1074
1075  Returns:
1076    String dedented by above rules.
1077
1078  For example:
1079    assertEquals("bar\nline1\nline2",   dedent("bar\n  line1\n  line2"))
1080    assertEquals("bar\nline1\nline2",   dedent(" bar\n  line1\n  line2"))
1081    assertEquals("bar\n  line1\nline2", dedent(" bar\n    line1\n  line2"))
1082  """
1083  text = textwrap.dedent(text)
1084  text_lines = text.split('\n')
1085  text_not_first = "\n".join(text_lines[1:])
1086  text_not_first = textwrap.dedent(text_not_first)
1087  text = text_lines[0] + "\n" + text_not_first
1088
1089  return text
1090
1091def md(text, img_src_prefix="", table_ext=True):
1092    """
1093    Run text through markdown to produce HTML.
1094
1095    This also removes all common indentation from every line but the 0th.
1096    This will avoid getting <code> blocks in markdown.
1097    Ignoring the 0th line will also allow the 0th line not to be aligned.
1098
1099    Args:
1100      text: A markdown-syntax using block of text to format.
1101      img_src_prefix: An optional string to prepend to each <img src="target"/>
1102
1103    Returns:
1104      String rendered by markdown and other rules applied (see above).
1105
1106    For example, this avoids the following situation:
1107
1108      <!-- Input -->
1109
1110      <!--- can't use dedent directly since 'foo' has no indent -->
1111      <notes>foo
1112          bar
1113          bar
1114      </notes>
1115
1116      <!-- Bad Output -- >
1117      <!-- if no dedent is done generated code looks like -->
1118      <p>foo
1119        <code><pre>
1120          bar
1121          bar</pre></code>
1122      </p>
1123
1124    Instead we get the more natural expected result:
1125
1126      <!-- Good Output -->
1127      <p>foo
1128      bar
1129      bar</p>
1130
1131    """
1132    text = dedent(text)
1133
1134    # full list of extensions at http://pythonhosted.org/Markdown/extensions/
1135    md_extensions = ['tables'] if table_ext else []# make <table> with ASCII |_| tables
1136    # render with markdown
1137    text = markdown.markdown(text, md_extensions)
1138
1139    # prepend a prefix to each <img src="foo"> -> <img src="${prefix}foo">
1140    text = re.sub(r'src="([^"]*)"', 'src="' + img_src_prefix + r'\1"', text)
1141    return text
1142
1143def filter_tags(text, metadata, filter_function, summary_function = None):
1144    """
1145    Find all references to tags in the form outer_namespace.xxx.yyy[.zzz] in
1146    the provided text, and pass them through filter_function and summary_function.
1147
1148    Used to linkify entry names in HMTL, javadoc output.
1149
1150    Args:
1151      text: A string representing a block of text destined for output
1152      metadata: A Metadata instance, the root of the metadata properties tree
1153      filter_function: A Node->string function to apply to each node
1154        when found in text; the string returned replaces the tag name in text.
1155      summary_function: A Node list->string function that is provided the list of
1156        unique tag nodes found in text, and which must return a string that is
1157        then appended to the end of the text. The list is sorted alphabetically
1158        by node name.
1159    """
1160
1161    tag_set = set()
1162    def name_match(name):
1163      return lambda node: node.name == name
1164
1165    # Match outer_namespace.x.y or outer_namespace.x.y.z, making sure
1166    # to grab .z and not just outer_namespace.x.y.  (sloppy, but since we
1167    # check for validity, a few false positives don't hurt).
1168    # Try to ignore items of the form {@link <outer_namespace>...
1169    for outer_namespace in metadata.outer_namespaces:
1170
1171      tag_match = r"(?<!\{@link\s)" + outer_namespace.name + \
1172        r"\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)(\.[a-zA-Z0-9\n]+)?([/]?)"
1173
1174      def filter_sub(match):
1175        whole_match = match.group(0)
1176        section1 = match.group(1)
1177        section2 = match.group(2)
1178        section3 = match.group(3)
1179        end_slash = match.group(4)
1180
1181        # Don't linkify things ending in slash (urls, for example)
1182        if end_slash:
1183          return whole_match
1184
1185        candidate = ""
1186
1187        # First try a two-level match
1188        candidate2 = "%s.%s.%s" % (outer_namespace.name, section1, section2)
1189        got_two_level = False
1190
1191        node = metadata.find_first(name_match(candidate2.replace('\n','')))
1192        if not node and '\n' in section2:
1193          # Linefeeds are ambiguous - was the intent to add a space,
1194          # or continue a lengthy name? Try the former now.
1195          candidate2b = "%s.%s.%s" % (outer_namespace.name, section1, section2[:section2.find('\n')])
1196          node = metadata.find_first(name_match(candidate2b))
1197          if node:
1198            candidate2 = candidate2b
1199
1200        if node:
1201          # Have two-level match
1202          got_two_level = True
1203          candidate = candidate2
1204        elif section3:
1205          # Try three-level match
1206          candidate3 = "%s%s" % (candidate2, section3)
1207          node = metadata.find_first(name_match(candidate3.replace('\n','')))
1208
1209          if not node and '\n' in section3:
1210            # Linefeeds are ambiguous - was the intent to add a space,
1211            # or continue a lengthy name? Try the former now.
1212            candidate3b = "%s%s" % (candidate2, section3[:section3.find('\n')])
1213            node = metadata.find_first(name_match(candidate3b))
1214            if node:
1215              candidate3 = candidate3b
1216
1217          if node:
1218            # Have 3-level match
1219            candidate = candidate3
1220
1221        # Replace match with crossref or complain if a likely match couldn't be matched
1222
1223        if node:
1224          tag_set.add(node)
1225          return whole_match.replace(candidate,filter_function(node))
1226        else:
1227          print >> sys.stderr,\
1228            "  WARNING: Could not crossref likely reference {%s}" % (match.group(0))
1229          return whole_match
1230
1231      text = re.sub(tag_match, filter_sub, text)
1232
1233    if summary_function is not None:
1234      return text + summary_function(sorted(tag_set, key=lambda x: x.name))
1235    else:
1236      return text
1237
1238def ndk_replace_tag_wildcards(text, metadata):
1239    """
1240    Find all references to tags in the form android.xxx.* or android.xxx.yyy.*
1241    in the provided text, and replace them by NDK format of "ACAMERA_XXX_*" or
1242    "ACAMERA_XXX_YYY_*"
1243
1244    Args:
1245      text: A string representing a block of text destined for output
1246      metadata: A Metadata instance, the root of the metadata properties tree
1247    """
1248    tag_match = r"android\.([a-zA-Z0-9\n]+)\.\*"
1249    tag_match_2 = r"android\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)\*"
1250
1251    def filter_sub(match):
1252      return "ACAMERA_" + match.group(1).upper() + "_*"
1253    def filter_sub_2(match):
1254      return "ACAMERA_" + match.group(1).upper() + match.group(2).upper() + "_*"
1255
1256    text = re.sub(tag_match, filter_sub, text)
1257    text = re.sub(tag_match_2, filter_sub_2, text)
1258    return text
1259
1260def filter_links(text, filter_function, summary_function = None):
1261    """
1262    Find all references to tags in the form {@link xxx#yyy [zzz]} in the
1263    provided text, and pass them through filter_function and
1264    summary_function.
1265
1266    Used to linkify documentation cross-references in HMTL, javadoc output.
1267
1268    Args:
1269      text: A string representing a block of text destined for output
1270      metadata: A Metadata instance, the root of the metadata properties tree
1271      filter_function: A (string, string)->string function to apply to each 'xxx#yyy',
1272        zzz pair when found in text; the string returned replaces the tag name in text.
1273      summary_function: A string list->string function that is provided the list of
1274        unique targets found in text, and which must return a string that is
1275        then appended to the end of the text. The list is sorted alphabetically
1276        by node name.
1277
1278    """
1279
1280    target_set = set()
1281    def name_match(name):
1282      return lambda node: node.name == name
1283
1284    tag_match = r"\{@link\s+([^\s\}\|]+)(?:\|([^\s\}]+))*([^\}]*)\}"
1285
1286    def filter_sub(match):
1287      whole_match = match.group(0)
1288      target = match.group(1)
1289      target_ndk = match.group(2)
1290      shortname = match.group(3).strip()
1291
1292      #print "Found link '%s' ndk '%s' as '%s' -> '%s'" % (target, target_ndk, shortname, filter_function(target, target_ndk, shortname))
1293
1294      # Replace match with crossref
1295      target_set.add(target)
1296      return filter_function(target, target_ndk, shortname)
1297
1298    text = re.sub(tag_match, filter_sub, text)
1299
1300    if summary_function is not None:
1301      return text + summary_function(sorted(target_set))
1302    else:
1303      return text
1304
1305def any_visible(section, kind_name, visibilities):
1306  """
1307  Determine if entries in this section have an applied visibility that's in
1308  the list of given visibilities.
1309
1310  Args:
1311    section: A section of metadata
1312    kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
1313    visibilities: An iterable of visibilities to match against
1314
1315  Returns:
1316    True if the section has any entries with any of the given visibilities. False otherwise.
1317  """
1318
1319  for inner_namespace in get_children_by_filtering_kind(section, kind_name,
1320                                                        'namespaces'):
1321    if any(filter_visibility(inner_namespace.merged_entries, visibilities)):
1322      return True
1323
1324  return any(filter_visibility(get_children_by_filtering_kind(section, kind_name,
1325                                                              'merged_entries'),
1326                               visibilities))
1327
1328
1329def filter_visibility(entries, visibilities):
1330  """
1331  Remove entries whose applied visibility is not in the supplied visibilities.
1332
1333  Args:
1334    entries: An iterable of Entry nodes
1335    visibilities: An iterable of visibilities to filter against
1336
1337  Yields:
1338    An iterable of Entry nodes
1339  """
1340  return (e for e in entries if e.applied_visibility in visibilities)
1341
1342def remove_synthetic(entries):
1343  """
1344  Filter the given entries by removing those that are synthetic.
1345
1346  Args:
1347    entries: An iterable of Entry nodes
1348
1349  Yields:
1350    An iterable of Entry nodes
1351  """
1352  return (e for e in entries if not e.synthetic)
1353
1354def filter_added_in_hal_version(entries, hal_major_version, hal_minor_version):
1355  """
1356  Filter the given entries to those added in the given HIDL HAL version
1357
1358  Args:
1359    entries: An iterable of Entry nodes
1360    hal_major_version: Major HIDL version to filter for
1361    hal_minor_version: Minor HIDL version to filter for
1362
1363  Yields:
1364    An iterable of Entry nodes
1365  """
1366  return (e for e in entries if e.hal_major_version == hal_major_version and e.hal_minor_version == hal_minor_version)
1367
1368def filter_has_enum_values_added_in_hal_version(entries, hal_major_version, hal_minor_version):
1369  """
1370  Filter the given entries to those that have a new enum value added in the given HIDL HAL version
1371
1372  Args:
1373    entries: An iterable of Entry nodes
1374    hal_major_version: Major HIDL version to filter for
1375    hal_minor_version: Minor HIDL version to filter for
1376
1377  Yields:
1378    An iterable of Entry nodes
1379  """
1380  return (e for e in entries if e.has_new_values_added_in_hal_version(hal_major_version, hal_minor_version))
1381
1382def filter_ndk_visible(entries):
1383  """
1384  Filter the given entries by removing those that are not NDK visible.
1385
1386  Args:
1387    entries: An iterable of Entry nodes
1388
1389  Yields:
1390    An iterable of Entry nodes
1391  """
1392  return (e for e in entries if e.applied_ndk_visible == 'true')
1393
1394def wbr(text):
1395  """
1396  Insert word break hints for the browser in the form of <wbr> HTML tags.
1397
1398  Word breaks are inserted inside an HTML node only, so the nodes themselves
1399  will not be changed. Attributes are also left unchanged.
1400
1401  The following rules apply to insert word breaks:
1402  - For characters in [ '.', '/', '_' ]
1403  - For uppercase letters inside a multi-word X.Y.Z (at least 3 parts)
1404
1405  Args:
1406    text: A string of text containing HTML content.
1407
1408  Returns:
1409    A string with <wbr> inserted by the above rules.
1410  """
1411  SPLIT_CHARS_LIST = ['.', '_', '/']
1412  SPLIT_CHARS = r'([.|/|_/,]+)' # split by these characters
1413  CAP_LETTER_MIN = 3 # at least 3 components split by above chars, i.e. x.y.z
1414  def wbr_filter(text):
1415      new_txt = text
1416
1417      # for johnyOrange.appleCider.redGuardian also insert wbr before the caps
1418      # => johny<wbr>Orange.apple<wbr>Cider.red<wbr>Guardian
1419      for words in text.split(" "):
1420        for char in SPLIT_CHARS_LIST:
1421          # match at least x.y.z, don't match x or x.y
1422          if len(words.split(char)) >= CAP_LETTER_MIN:
1423            new_word = re.sub(r"([a-z])([A-Z])", r"\1<wbr>\2", words)
1424            new_txt = new_txt.replace(words, new_word)
1425
1426      # e.g. X/Y/Z -> X/<wbr>Y/<wbr>/Z. also for X.Y.Z, X_Y_Z.
1427      new_txt = re.sub(SPLIT_CHARS, r"\1<wbr>", new_txt)
1428
1429      return new_txt
1430
1431  # Do not mangle HTML when doing the replace by using BeatifulSoup
1432  # - Use the 'html.parser' to avoid inserting <html><body> when decoding
1433  soup = bs4.BeautifulSoup(text, features='html.parser')
1434  wbr_tag = lambda: soup.new_tag('wbr') # must generate new tag every time
1435
1436  for navigable_string in soup.findAll(text=True):
1437      parent = navigable_string.parent
1438
1439      # Insert each '$text<wbr>$foo' before the old '$text$foo'
1440      split_by_wbr_list = wbr_filter(navigable_string).split("<wbr>")
1441      for (split_string, last) in enumerate_with_last(split_by_wbr_list):
1442          navigable_string.insert_before(split_string)
1443
1444          if not last:
1445            # Note that 'insert' will move existing tags to this spot
1446            # so make a new tag instead
1447            navigable_string.insert_before(wbr_tag())
1448
1449      # Remove the old unmodified text
1450      navigable_string.extract()
1451
1452  return soup.decode()
1453
1454def hal_major_version():
1455  return _hal_major_version
1456
1457def hal_minor_version():
1458  return _hal_minor_version
1459
1460def first_hal_minor_version(hal_major_version):
1461  return 2 if hal_major_version == 3 else 0
1462
1463def find_all_sections_added_in_hal(root, hal_major_version, hal_minor_version):
1464  """
1465  Find all descendants that are Section or InnerNamespace instances, which
1466  were added in HIDL HAL version major.minor. The section is defined to be
1467  added in a HAL version iff the lowest HAL version number of its entries is
1468  that HAL version.
1469
1470  Args:
1471    root: a Metadata instance
1472    hal_major/minor_version: HAL version numbers
1473
1474  Returns:
1475    A list of Section/InnerNamespace instances
1476
1477  Remarks:
1478    These are known as "sections" in the generated C code.
1479  """
1480  all_sections = find_all_sections(root)
1481  new_sections = []
1482  for section in all_sections:
1483    min_major_version = None
1484    min_minor_version = None
1485    for entry in remove_synthetic(find_unique_entries(section)):
1486      min_major_version = (min_major_version or entry.hal_major_version)
1487      min_minor_version = (min_minor_version or entry.hal_minor_version)
1488      if entry.hal_major_version < min_major_version or \
1489          (entry.hal_major_version == min_major_version and entry.hal_minor_version < min_minor_version):
1490        min_minor_version = entry.hal_minor_version
1491        min_major_version = entry.hal_major_version
1492    if min_major_version == hal_major_version and min_minor_version == hal_minor_version:
1493      new_sections.append(section)
1494  return new_sections
1495
1496def find_first_older_used_hal_version(section, hal_major_version, hal_minor_version):
1497  hal_version = (0, 0)
1498  for v in section.hal_versions:
1499    if (v[0] > hal_version[0] or (v[0] == hal_version[0] and v[1] > hal_version[1])) and \
1500        (v[0] < hal_major_version or (v[0] == hal_major_version and v[1] < hal_minor_version)):
1501      hal_version = v
1502  return hal_version
1503