chromium/tools/json_to_struct/java_element_generator.py

# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import json
import class_generator

_INDENT = '    '


def _GenerateString(content, indent='  '):
  """Generates an UTF-8 string to be included in a static structure initializer.
  If content is not specified, uses NULL.
  """
  if content is None:
    return indent + 'null,'
  else:
    # json.dumps quotes the string and escape characters as required.
    return indent + '%s,' % json.dumps(content)


def _GenerateArrayVariableName(element_name, field_name, field_name_count):
  """Generates a unique variable name for an array variable.
  """
  var = '%s_%s' % (element_name, field_name)
  if var not in field_name_count:
    field_name_count[var] = 0
    return var
  new_var = '%s_%d' % (var, field_name_count[var])
  field_name_count[var] += 1
  return new_var


def _GenerateArray(element_name, field_info, content, indent, field_name_count):
  """Generates an array created inline in a constructor call. If content is
  not specified, null is used.
  """
  if content is None:
    return indent + 'null,'

  lines = []

  array_field = class_generator.GenerateField(field_info['contents'])
  array_type = array_field[:array_field.find(' ')]
  lines.append(indent + 'new %s[]{' % array_type)
  for subcontent in content:
    lines.append(
        _GenerateFieldContent(element_name, field_info['contents'], subcontent,
                              indent + _INDENT, field_name_count))
  lines.append(indent + '},')
  return '\n'.join(lines)


def _GenerateClass(element_name, field_info, content, indent, field_name_count):
  """Generates a class to be used in a constructor call. If content is not
  specified, use null.
  """
  if content is None:
    return indent + 'null'

  lines = []

  fields = field_info['fields']
  class_name = field_info['type_name']
  lines.append(indent + 'new ' + class_name + '(')
  for field in fields:
    subcontent = content.get(field['field'])
    lines.append(
        _GenerateFieldContent(element_name, field, subcontent, indent + _INDENT,
                              field_name_count))

  # remove the trailing comma for the last parameter
  lines[-1] = lines[-1][:-1]
  lines.append(indent + '),')

  return '\n'.join(lines)


def _GenerateFieldContent(element_name, field_info, content, indent,
                          field_name_count):
  """Generate the content of a field to be included in the constructor call. If
  the field's content is not specified, uses the default value if one exists.
  """
  if content is None:
    content = field_info.get('java_default', None)

  java_type = field_info['type']
  if java_type in ('int', 'class'):
    return '%s%s,' % (indent, content)
  elif java_type == 'enum':
    # TODO(peilinwang) temporarily treat enums as strings. Maybe use a
    # different schema? Right now these scripts are only used for generating
    # fieldtrial testing configs.
    return '%s"%s",' % (indent, content)
  elif java_type == 'string':
    return _GenerateString(content, indent)
  elif java_type == 'string16':
    raise RuntimeError('Generating a UTF16 java String is not supported yet.')
  elif java_type == 'array':
    return _GenerateArray(element_name, field_info, content, indent,
                          field_name_count)
  elif java_type == 'struct':
    return _GenerateClass(element_name, field_info, content, indent,
                          field_name_count)
  else:
    raise RuntimeError('Unknown field type "%s"' % java_type)


def _GenerateElement(type_name, schema, element_name, element,
                     field_name_count):
  """Generate the constructor call for one element.
  """
  lines = []
  lines.append(_INDENT + 'public static final %s %s = ' %
               (type_name, element_name))
  lines.append(2 * _INDENT + 'new %s(' % (type_name))
  for field_info in schema:
    content = element.get(field_info['field'], None)
    if (content == None and not field_info.get('optional', False)):
      raise RuntimeError('Mandatory field "%s" omitted in element "%s".' %
                         (field_info['field'], element_name))
    lines.append(
        _GenerateFieldContent(element_name, field_info, content, 2 * _INDENT,
                              field_name_count))

  # remove the trailing comma for the last parameter
  lines[-1] = lines[-1][:-1]
  lines.append(2 * _INDENT + ');')
  return '\n'.join(lines)


def GenerateElements(type_name, schema, description, field_name_count={}):
  """Generate the static initializers for all the elements in the
  description['elements'] dictionary, as well as for any variables in
  description['int_variables']. All elements in description['elements']
  will be generated as a public static final int/class.
  """
  result = []
  for var_name, value in description.get('int_variables', {}).items():
    result.append('public static final int %s = %s;' % (var_name, value))
  result.append('')

  for element_name, element in description.get('elements', {}).items():
    result.append(
        _GenerateElement(type_name, schema, element_name, element,
                         field_name_count))
    result.append('')
  return '\n'.join(result)