/*******************************************************************\

Module: JAVA Bytecode Language Conversion

Author: Daniel Kroening, kroening@kroening.com

\*******************************************************************/

/// \file
/// JAVA Bytecode Language Conversion

#include "java_bytecode_convert_class.h"

#ifdef DEBUG
#include <iostream>
#endif

#include "java_root_class.h"
#include "java_types.h"
#include "java_bytecode_convert_method.h"
#include "java_bytecode_language.h"
#include "java_utils.h"

#include <util/c_types.h>
#include <util/arith_tools.h>
#include <util/namespace.h>
#include <util/std_expr.h>

#include <linking/zero_initializer.h>
#include <util/suffix.h>

class java_bytecode_convert_classt:public messaget
{
public:
  java_bytecode_convert_classt(
    symbol_tablet &_symbol_table,
    message_handlert &_message_handler,
    size_t _max_array_length,
    method_bytecodet &method_bytecode,
    java_string_library_preprocesst &_string_preprocess)
    : messaget(_message_handler),
      symbol_table(_symbol_table),
      max_array_length(_max_array_length),
      method_bytecode(method_bytecode),
      string_preprocess(_string_preprocess)
  {
  }

  void operator()(
    const java_class_loadert::parse_tree_with_overlayst &parse_trees);

  typedef java_bytecode_parse_treet::classt classt;
  typedef java_bytecode_parse_treet::fieldt fieldt;
  typedef java_bytecode_parse_treet::methodt methodt;
  typedef java_bytecode_parse_treet::annotationt annotationt;

private:
  symbol_tablet &symbol_table;
  const size_t max_array_length;
  method_bytecodet &method_bytecode;
  java_string_library_preprocesst &string_preprocess;

  // conversion
  typedef std::list<std::reference_wrapper<const classt>> overlay_classest;
  void convert(const classt &c, const overlay_classest &overlay_classes);
  void convert(symbolt &class_symbol, const fieldt &f);

  // see definition below for more info
  static void add_array_types(symbol_tablet &symbol_table);
  static bool is_overlay_class(const classt &c);
  static bool is_overlay_method(const methodt &method);

  bool check_field_exists(
    const fieldt &field,
    const irep_idt &qualified_fieldname,
    const struct_union_typet::componentst &fields) const;
};

/// Auxiliary function to extract the generic superclass reference from the
/// class signature. If the signature is empty or the superclass is not generic
/// it returns empty.
/// Example:
/// - class: A<T> extends B<T, Integer> implements C, D<T>
/// - signature: <T:Ljava/lang/Object;>B<TT;Ljava/lang/Integer;>;LC;LD<TT;>;
/// - returned superclass reference: B<TT;Ljava/lang/Integer;>;
/// \param signature Signature of the class
/// \return Reference of the generic superclass, or empty if the superclass
/// is not generic
static optionalt<std::string>
extract_generic_superclass_reference(const optionalt<std::string> &signature)
{
  if(signature.has_value())
  {
    // skip the (potential) list of generic parameters at the beginning of the
    // signature
    const size_t start =
      signature.value().front() == '<'
        ? find_closing_delimiter(signature.value(), 0, '<', '>') + 1
        : 0;

    // extract the superclass reference
    const size_t end =
      find_closing_semi_colon_for_reference_type(signature.value(), start);
    const std::string superclass_ref =
      signature.value().substr(start, (end - start) + 1);

    // if the superclass is generic then the reference is of form
    // `Lsuperclass-name<generic-types;>;` if it is implicitly generic, then the
    // reference is of the form
    // `Lsuperclass-name<Tgeneric-types;>.Innerclass-Name`
    if(superclass_ref.find('<') != std::string::npos)
      return superclass_ref;
  }
  return {};
}

/// Auxiliary function to extract the generic interface reference of an
/// interface with the specified name from the class signature. If the
/// signature is empty or the interface is not generic it returns empty.
/// Example:
/// - class: A<T> extends B<T, Integer> implements C, D<T>
/// - signature: <T:Ljava/lang/Object;>B<TT;Ljava/lang/Integer;>;LC;LD<TT;>;
/// - returned interface reference for C: LC;
/// - returned interface reference for D: LD<TT;>;
/// \param signature Signature of the class
/// \param interface_name The interface name
/// \return Reference of the generic interface, or empty if the interface
/// is not generic
static optionalt<std::string> extract_generic_interface_reference(
  const optionalt<std::string> &signature,
  const std::string &interface_name)
{
  if(signature.has_value())
  {
    // skip the (potential) list of generic parameters at the beginning of the
    // signature
    size_t start =
      signature.value().front() == '<'
        ? find_closing_delimiter(signature.value(), 0, '<', '>') + 1
        : 0;

    // skip the superclass reference (if there is at least one interface
    // reference in the signature, then there is a superclass reference)
    start =
      find_closing_semi_colon_for_reference_type(signature.value(), start) + 1;

    // if the interface name includes package name, convert dots to slashes
    std::string interface_name_slash_to_dot = interface_name;
    std::replace(
      interface_name_slash_to_dot.begin(),
      interface_name_slash_to_dot.end(),
      '.',
      '/');

    start =
      signature.value().find("L" + interface_name_slash_to_dot + "<", start);
    if(start != std::string::npos)
    {
      const size_t &end =
        find_closing_semi_colon_for_reference_type(signature.value(), start);
      return signature.value().substr(start, (end - start) + 1);
    }
  }
  return {};
}

/// Converts a class parse tree into a class symbol and adds it to the
/// symbol table.
/// \param parse_trees: The parse trees found for the class to be converted.
/// \remarks
///   Allows multiple definitions of the same class to appear on the
///   classpath, so long as all but the first definition are marked with the
///   attribute `\@java::com.diffblue.OverlayClassImplementation`.
///   Overlay class definitions can contain methods with the same signature
///   as methods in the original class, so long as these are marked with the
///   attribute `\@java::com.diffblue.OverlayMethodImplementation`; such
///   overlay methods are replaced in the original file with the version
///   found in the last overlay on the classpath. Later definitions can
///   also contain new supporting methods and fields that are merged in.
///   This will allow insertion of Java methods into library classes to
///   handle, for example, modelling dependency injection.
void java_bytecode_convert_classt::operator()(
  const java_class_loadert::parse_tree_with_overlayst &parse_trees)
{
  PRECONDITION(!parse_trees.empty());
  const irep_idt &class_name = parse_trees.front().parsed_class.name;

  if(symbol_table.has_symbol("java::" + id2string(class_name)))
  {
    debug() << "Skip class " << class_name << " (already loaded)" << eom;
    return;
  }

  // Add array types to the symbol table
  add_array_types(symbol_table);

  // Ignore all parse trees that failed to load
  std::list<std::reference_wrapper<const classt>> loaded_parse_trees;
  for(const java_bytecode_parse_treet &parse_tree : parse_trees)
  {
    if(parse_tree.loading_successful)
      loaded_parse_trees.push_back(std::cref(parse_tree.parsed_class));
  }
  auto parse_tree_it = loaded_parse_trees.begin();
  // If the first class implementation is an overlay emit a warning and
  // skip over it until we find a non-overlay class
  while(parse_tree_it != loaded_parse_trees.end())
  {
    const classt &parsed_class = *parse_tree_it;
    if(!is_overlay_class(parsed_class))
      break;
    warning()
      << "Skipping class " << parsed_class.name
      << " marked with OverlayClassImplementation but found before original"
        " definition"
      << eom;
    ++parse_tree_it;
  }
  bool loading_success = false;
  if(parse_tree_it != loaded_parse_trees.end())
  {
    // Collect overlay classes
    overlay_classest overlay_classes;
    for(auto overlay_class_it = std::next(parse_tree_it);
        overlay_class_it != loaded_parse_trees.end();
        ++overlay_class_it)
    {
      const classt &overlay_class = *overlay_class_it;
      // Ignore non-initial classes that aren't overlays
      if(!is_overlay_class(overlay_class))
      {
        warning()
          << "Skipping duplicate definition of class " << class_name
          << " not marked with OverlayClassImplementation" << eom;
        continue;
      }
      overlay_classes.push_front(std::cref(overlay_class));
    }
    const classt &parsed_class = *parse_tree_it;
    convert(parsed_class, overlay_classes);
    loading_success = true;
  }

  // Add as string type if relevant
  if(string_preprocess.is_known_string_type(class_name))
    string_preprocess.add_string_type(class_name, symbol_table);
  else if(!loading_success)
    // Generate stub if couldn't load from bytecode and wasn't string type
    generate_class_stub(
      class_name,
      symbol_table,
      get_message_handler(),
      struct_union_typet::componentst{});
}

/// Convert a class, adding symbols to the symbol table for its members
/// \param c: Bytecode of the class to convert
/// \param overlay_classes: Bytecode of any overlays for the class to convert
void java_bytecode_convert_classt::convert(
  const classt &c,
  const overlay_classest &overlay_classes)
{
  std::string qualified_classname = "java::" + id2string(c.name);

  java_class_typet class_type;
  if(c.signature.has_value() && c.signature.value()[0]=='<')
  {
    java_generic_class_typet generic_class_type;
#ifdef DEBUG
    std::cout << "INFO: found generic class signature "
              << c.signature.value()
              << " in parsed class "
              << c.name << "\n";
#endif
    try
    {
      const std::vector<typet> &generic_types=java_generic_type_from_string(
        id2string(c.name),
        c.signature.value());
      for(const typet &t : generic_types)
      {
        generic_class_type.generic_types()
          .push_back(to_java_generic_parameter(t));
      }
      class_type=generic_class_type;
    }
    catch(const unsupported_java_class_signature_exceptiont &e)
    {
      warning() << "Class: " << c.name
                << "\n could not parse signature: " << c.signature.value()
                << "\n " << e.what() << "\n ignoring that the class is generic"
                << eom;
    }
  }

  class_type.set_tag(c.name);
  class_type.set(ID_base_name, c.name);
  class_type.set(ID_abstract, c.is_abstract);
  if(c.is_enum)
  {
    if(max_array_length != 0 && c.enum_elements > max_array_length)
    {
      warning() << "Java Enum " << c.name << " won't work properly because max "
                << "array length (" << max_array_length << ") is less than the "
                << "enum size (" << c.enum_elements << ")" << eom;
    }
    class_type.set(
      ID_java_enum_static_unwind,
      std::to_string(c.enum_elements+1));
    class_type.set(ID_enumeration, true);
  }

  if(c.is_public)
    class_type.set_access(ID_public);
  else if(c.is_protected)
    class_type.set_access(ID_protected);
  else if(c.is_private)
    class_type.set_access(ID_private);
  else
    class_type.set_access(ID_default);

  if(!c.extends.empty())
  {
    const symbol_typet base("java::" + id2string(c.extends));

    // if the superclass is generic then the class has the superclass reference
    // including the generic info in its signature
    // e.g., signature for class 'A<T>' that extends
    // 'Generic<Integer>' is '<T:Ljava/lang/Object;>LGeneric<LInteger;>;'
    const optionalt<std::string> &superclass_ref =
      extract_generic_superclass_reference(c.signature);
    if(superclass_ref.has_value())
    {
      try
      {
        const java_generic_symbol_typet generic_base(
          base, superclass_ref.value(), qualified_classname);
        class_type.add_base(generic_base);
      }
      catch(const unsupported_java_class_signature_exceptiont &e)
      {
        warning() << "Superclass: " << c.extends << " of class: " << c.name
                  << "\n could not parse signature: " << superclass_ref.value()
                  << "\n " << e.what()
                  << "\n ignoring that the superclass is generic" << eom;
        class_type.add_base(base);
      }
    }
    else
    {
      class_type.add_base(base);
    }
    class_typet::componentt base_class_field;
    base_class_field.type() = class_type.bases().at(0).type();
    base_class_field.set_name("@"+id2string(c.extends));
    base_class_field.set_base_name("@"+id2string(c.extends));
    base_class_field.set_pretty_name("@"+id2string(c.extends));
    class_type.components().push_back(base_class_field);
  }

  // interfaces are recorded as bases
  for(const auto &interface : c.implements)
  {
    const symbol_typet base("java::" + id2string(interface));

    // if the interface is generic then the class has the interface reference
    // including the generic info in its signature
    // e.g., signature for class 'A implements GenericInterface<Integer>' is
    // 'Ljava/lang/Object;LGenericInterface<LInteger;>;'
    const optionalt<std::string> interface_ref =
      extract_generic_interface_reference(c.signature, id2string(interface));
    if(interface_ref.has_value())
    {
      try
      {
        const java_generic_symbol_typet generic_base(
          base, interface_ref.value(), qualified_classname);
        class_type.add_base(generic_base);
      }
      catch(const unsupported_java_class_signature_exceptiont &e)
      {
        warning() << "Interface: " << interface << " of class: " << c.name
                  << "\n could not parse signature: " << interface_ref.value()
                  << "\n " << e.what()
                  << "\n ignoring that the interface is generic" << eom;
        class_type.add_base(base);
      }
    }
    else
    {
      class_type.add_base(base);
    }
  }

  // now do lambda method handles (bootstrap methods)
  for(const auto &lambda_entry : c.lambda_method_handle_map)
  {
    // if the handle is of unknown type, we still need to store it to preserve
    // the correct indexing (invokedynamic instructions will retrieve
    // method handles by index)
    lambda_entry.second.is_unknown_handle()
      ? class_type.add_unknown_lambda_method_handle()
      : class_type.add_lambda_method_handle(
          "java::" + id2string(lambda_entry.second.lambda_method_ref));
  }

  // produce class symbol
  symbolt new_symbol;
  new_symbol.base_name=c.name;
  new_symbol.pretty_name=c.name;
  new_symbol.name=qualified_classname;
  class_type.set(ID_name, new_symbol.name);
  new_symbol.type=class_type;
  new_symbol.mode=ID_java;
  new_symbol.is_type=true;

  symbolt *class_symbol;

  // add before we do members
  debug() << "Adding symbol: class '" << c.name << "'" << eom;
  if(symbol_table.move(new_symbol, class_symbol))
  {
    error() << "failed to add class symbol " << new_symbol.name << eom;
    throw 0;
  }

  // Now do fields
  const class_typet::componentst &fields =
    to_class_type(class_symbol->type).components();
  // Include fields from overlay classes as they will be required by the
  // methods defined there
  for(auto overlay_class : overlay_classes)
  {
    for(const auto &field : overlay_class.get().fields)
    {
      std::string field_id = qualified_classname + "." + id2string(field.name);
      if(check_field_exists(field, field_id, fields))
      {
        std::string err =
          "Duplicate field definition for " + field_id + " in overlay class";
        // TODO: This could just be a warning if the types match
        error() << err << eom;
        throw err.c_str();
      }
      debug()
        << "Adding symbol from overlay class:  field '" << field.name << "'"
        << eom;
      convert(*class_symbol, field);
      POSTCONDITION(check_field_exists(field, field_id, fields));
    }
  }
  for(const auto &field : c.fields)
  {
    std::string field_id = qualified_classname + "." + id2string(field.name);
    if(check_field_exists(field, field_id, fields))
    {
      // TODO: This could be a warning if the types match
      error()
        << "Field definition for " << field_id
        << " already loaded from overlay class" << eom;
      continue;
    }
    debug() << "Adding symbol:  field '" << field.name << "'" << eom;
    convert(*class_symbol, field);
    POSTCONDITION(check_field_exists(field, field_id, fields));
  }

  // Now do methods
  std::set<irep_idt> overlay_methods;
  for(auto overlay_class : overlay_classes)
  {
    for(const methodt &method : overlay_class.get().methods)
    {
      const irep_idt method_identifier =
        qualified_classname + "." + id2string(method.name)
          + ":" + method.descriptor;
      if(method_bytecode.contains_method(method_identifier))
      {
        // This method has already been discovered and added to method_bytecode
        // while parsing an overlay class that appears later in the classpath
        // (we're working backwards)
        // Warn the user if the definition already found was not an overlay,
        // otherwise simply don't load this earlier definition
        if(overlay_methods.count(method_identifier) == 0)
        {
          // This method was defined in a previous class definition without
          // being marked as an overlay method
          warning()
            << "Method " << method_identifier
            << " exists in an overlay class without being marked as an "
              "overlay and also exists in another overlay class that appears "
              "earlier in the classpath"
            << eom;
        }
        continue;
      }
      // Always run the lazy pre-stage, as it symbol-table
      // registers the function.
      debug()
        << "Adding symbol from overlay class:  method '" << method_identifier
        << "'" << eom;
      java_bytecode_convert_method_lazy(
        *class_symbol,
        method_identifier,
        method,
        symbol_table,
        get_message_handler());
      method_bytecode.add(qualified_classname, method_identifier, method);
      if(is_overlay_method(method))
        overlay_methods.insert(method_identifier);
    }
  }
  for(const methodt &method : c.methods)
  {
    const irep_idt method_identifier=
      qualified_classname + "." + id2string(method.name)
        + ":" + method.descriptor;
    if(method_bytecode.contains_method(method_identifier))
    {
      // This method has already been discovered while parsing an overlay
      // class
      // If that definition is an overlay then we simply don't load this
      // original definition and we remove it from the list of overlays
      if(overlay_methods.erase(method_identifier) == 0)
      {
        // This method was defined in a previous class definition without
        // being marked as an overlay method
        warning()
          << "Method " << method_identifier
          << " exists in an overlay class without being marked as an overlay "
            "and also exists in the underlying class"
          << eom;
      }
      continue;
    }
    // Always run the lazy pre-stage, as it symbol-table
    // registers the function.
    debug() << "Adding symbol:  method '" << method_identifier << "'" << eom;
    java_bytecode_convert_method_lazy(
      *class_symbol,
      method_identifier,
      method,
      symbol_table,
      get_message_handler());
    method_bytecode.add(qualified_classname, method_identifier, method);
    if(is_overlay_method(method))
    {
      warning()
        << "Method " << method_identifier
        << " marked as an overlay where defined in the underlying class" << eom;
    }
  }
  if(!overlay_methods.empty())
  {
    error()
      << "Overlay methods defined in overlay classes did not exist in the "
        "underlying class:\n";
    for(const irep_idt &method_id : overlay_methods)
      error() << "  " << method_id << "\n";
    error() << eom;
  }

  // is this a root class?
  if(c.extends.empty())
    java_root_class(*class_symbol);
}

bool java_bytecode_convert_classt::check_field_exists(
  const java_bytecode_parse_treet::fieldt &field,
  const irep_idt &qualified_fieldname,
  const struct_union_typet::componentst &fields) const
{
  if(field.is_static)
    return symbol_table.has_symbol(qualified_fieldname);

  auto existing_field = std::find_if(
    fields.begin(),
    fields.end(),
    [&field](const struct_union_typet::componentt &f)
    {
      return f.get_name() == field.name;
    });
  return existing_field != fields.end();
}

/// Convert a field, adding a symbol to teh symbol table for it
/// \param class_symbol: The already added symbol for the containing class
/// \param f: The bytecode for the field to convert
void java_bytecode_convert_classt::convert(
  symbolt &class_symbol,
  const fieldt &f)
{
  typet field_type;
  if(f.signature.has_value())
  {
    field_type=java_type_from_string_with_exception(
      f.descriptor,
      f.signature,
      id2string(class_symbol.name));

    /// this is for a free type variable, e.g., a field of the form `T f;`
    if(is_java_generic_parameter(field_type))
    {
#ifdef DEBUG
      std::cout << "fieldtype: generic "
                << to_java_generic_parameter(field_type).type_variable()
                     .get_identifier()
                << " name " << f.name << "\n";
#endif
    }

    /// this is for a field that holds a generic type, either with instantiated
    /// or with free type variables, e.g., `List<T> l;` or `List<Integer> l;`
    else if(is_java_generic_type(field_type))
    {
      java_generic_typet &with_gen_type=
        to_java_generic_type(field_type);
#ifdef DEBUG
      std::cout << "fieldtype: generic container type "
                << std::to_string(with_gen_type.generic_type_arguments().size())
                << " type " << with_gen_type.id()
                << " name " << f.name
                << " subtype id " << with_gen_type.subtype().id() << "\n";
#endif
      field_type=with_gen_type;
    }
  }
  else
    field_type=java_type_from_string(f.descriptor);

  // is this a static field?
  if(f.is_static)
  {
    // Create the symbol; we won't add to the struct type.
    symbolt new_symbol;

    new_symbol.is_static_lifetime=true;
    new_symbol.is_lvalue=true;
    new_symbol.is_state_var=true;
    new_symbol.name=id2string(class_symbol.name)+"."+id2string(f.name);
    new_symbol.base_name=f.name;
    new_symbol.type=field_type;
    // Annotating the type with ID_C_class to provide a static field -> class
    // link matches the method used by java_bytecode_convert_method::convert
    // for methods.
    new_symbol.type.set(ID_C_class, class_symbol.name);
    new_symbol.pretty_name=id2string(class_symbol.pretty_name)+
      "."+id2string(f.name);
    new_symbol.mode=ID_java;
    new_symbol.is_type=false;

    // These annotations use `ID_C_access` instead of `ID_access` like methods
    // to avoid type clashes in expressions like `some_static_field = 0`, where
    // with ID_access the constant '0' would need to have an access modifier
    // too, or else appear to have incompatible type.
    if(f.is_public)
      new_symbol.type.set(ID_C_access, ID_public);
    else if(f.is_protected)
      new_symbol.type.set(ID_C_access, ID_protected);
    else if(f.is_private)
      new_symbol.type.set(ID_C_access, ID_private);
    else
      new_symbol.type.set(ID_C_access, ID_default);

    const namespacet ns(symbol_table);
    new_symbol.value=
      zero_initializer(
        field_type,
        class_symbol.location,
        ns,
        get_message_handler());

    // Do we have the static field symbol already?
    const auto s_it=symbol_table.symbols.find(new_symbol.name);
    if(s_it!=symbol_table.symbols.end())
      symbol_table.erase(s_it); // erase, we stubbed it

    if(symbol_table.add(new_symbol))
      assert(false && "failed to add static field symbol");
  }
  else
  {
    class_typet &class_type=to_class_type(class_symbol.type);

    class_type.components().push_back(class_typet::componentt());
    class_typet::componentt &component=class_type.components().back();

    component.set_name(f.name);
    component.set_base_name(f.name);
    component.set_pretty_name(f.name);
    component.type()=field_type;

    if(f.is_private)
      component.set_access(ID_private);
    else if(f.is_protected)
      component.set_access(ID_protected);
    else if(f.is_public)
      component.set_access(ID_public);
    else
      component.set_access(ID_default);
  }
}

/// Register in the \p symbol_table new symbols for the objects
/// java::array[X] where X is byte, float, int, char...
void java_bytecode_convert_classt::add_array_types(symbol_tablet &symbol_table)
{
  const std::string letters="ijsbcfdza";

  for(const char l : letters)
  {
    symbol_typet symbol_type=
      to_symbol_type(java_array_type(l).subtype());

    const irep_idt &symbol_type_identifier=symbol_type.get_identifier();
    if(symbol_table.has_symbol(symbol_type_identifier))
      return;

    class_typet class_type;
    // we have the base class, java.lang.Object, length and data
    // of appropriate type
    class_type.set_tag(symbol_type_identifier);

    class_type.components().reserve(3);
    class_typet::componentt base_class_component(
      "@java.lang.Object", symbol_typet("java::java.lang.Object"));
    base_class_component.set_pretty_name("@java.lang.Object");
    base_class_component.set_base_name("@java.lang.Object");
    class_type.components().push_back(base_class_component);

    class_typet::componentt length_component("length", java_int_type());
    length_component.set_pretty_name("length");
    length_component.set_base_name("length");
    class_type.components().push_back(length_component);

    class_typet::componentt data_component(
      "data", java_reference_type(java_type_from_char(l)));
    data_component.set_pretty_name("data");
    data_component.set_base_name("data");
    class_type.components().push_back(data_component);

    class_type.add_base(symbol_typet("java::java.lang.Object"));

    INVARIANT(
      is_valid_java_array(class_type),
      "Constructed a new type representing a Java Array "
      "object that doesn't match expectations");

    symbolt symbol;
    symbol.name=symbol_type_identifier;
    symbol.base_name=symbol_type.get(ID_C_base_name);
    symbol.is_type=true;
    symbol.type = class_type;
    symbol_table.add(symbol);

    // Also provide a clone method:
    // ----------------------------

    const irep_idt clone_name=
      id2string(symbol_type_identifier)+".clone:()Ljava/lang/Object;";
    code_typet clone_type;
    clone_type.return_type()=
      java_reference_type(symbol_typet("java::java.lang.Object"));
    code_typet::parametert this_param;
    this_param.set_identifier(id2string(clone_name)+"::this");
    this_param.set_base_name("this");
    this_param.set_this();
    this_param.type()=java_reference_type(symbol_type);
    clone_type.parameters().push_back(this_param);

    parameter_symbolt this_symbol;
    this_symbol.name=this_param.get_identifier();
    this_symbol.base_name=this_param.get_base_name();
    this_symbol.pretty_name=this_symbol.base_name;
    this_symbol.type=this_param.type();
    this_symbol.mode=ID_java;
    symbol_table.add(this_symbol);

    const irep_idt local_name=
      id2string(clone_name)+"::cloned_array";
    auxiliary_symbolt local_symbol;
    local_symbol.name=local_name;
    local_symbol.base_name="cloned_array";
    local_symbol.pretty_name=local_symbol.base_name;
    local_symbol.type=java_reference_type(symbol_type);
    local_symbol.mode=ID_java;
    symbol_table.add(local_symbol);
    const auto &local_symexpr=local_symbol.symbol_expr();

    code_blockt clone_body;

    code_declt declare_cloned(local_symexpr);
    clone_body.move_to_operands(declare_cloned);

    side_effect_exprt java_new_array(
      ID_java_new_array,
      java_reference_type(symbol_type));
    dereference_exprt old_array(this_symbol.symbol_expr(), symbol_type);
    dereference_exprt new_array(local_symexpr, symbol_type);
    member_exprt old_length(
      old_array, length_component.get_name(), length_component.type());
    java_new_array.copy_to_operands(old_length);
    code_assignt create_blank(local_symexpr, java_new_array);
    clone_body.move_to_operands(create_blank);

    member_exprt old_data(
      old_array, data_component.get_name(), data_component.type());
    member_exprt new_data(
      new_array, data_component.get_name(), data_component.type());

    /*
      // TODO use this instead of a loop.
    codet array_copy;
    array_copy.set_statement(ID_array_copy);
    array_copy.move_to_operands(new_data);
    array_copy.move_to_operands(old_data);
    clone_body.move_to_operands(array_copy);
    */

    // Begin for-loop to replace:

    const irep_idt index_name=
      id2string(clone_name)+"::index";
    auxiliary_symbolt index_symbol;
    index_symbol.name=index_name;
    index_symbol.base_name="index";
    index_symbol.pretty_name=index_symbol.base_name;
    index_symbol.type = length_component.type();
    index_symbol.mode=ID_java;
    symbol_table.add(index_symbol);
    const auto &index_symexpr=index_symbol.symbol_expr();

    code_declt declare_index(index_symexpr);
    clone_body.move_to_operands(declare_index);

    code_fort copy_loop;

    copy_loop.init()=
      code_assignt(index_symexpr, from_integer(0, index_symexpr.type()));
    copy_loop.cond()=
      binary_relation_exprt(index_symexpr, ID_lt, old_length);

    side_effect_exprt inc(ID_assign);
    inc.operands().resize(2);
    inc.op0()=index_symexpr;
    inc.op1()=plus_exprt(index_symexpr, from_integer(1, index_symexpr.type()));
    copy_loop.iter()=inc;

    dereference_exprt old_cell(
      plus_exprt(old_data, index_symexpr), old_data.type().subtype());
    dereference_exprt new_cell(
      plus_exprt(new_data, index_symexpr), new_data.type().subtype());
    code_assignt copy_cell(new_cell, old_cell);
    copy_loop.body()=copy_cell;

    // End for-loop
    clone_body.move_to_operands(copy_loop);

    member_exprt new_base_class(
      new_array, base_class_component.get_name(), base_class_component.type());
    address_of_exprt retval(new_base_class);
    code_returnt return_inst(retval);
    clone_body.move_to_operands(return_inst);

    symbolt clone_symbol;
    clone_symbol.name=clone_name;
    clone_symbol.pretty_name=
      id2string(symbol_type_identifier)+".clone:()";
    clone_symbol.base_name="clone";
    clone_symbol.type=clone_type;
    clone_symbol.value=clone_body;
    clone_symbol.mode=ID_java;
    symbol_table.add(clone_symbol);
  }
}

static bool does_annotation_exist(
  java_bytecode_parse_treet::annotationst annotations,
  irep_idt annotation_type_name)
{
  return
    std::find_if(
      annotations.begin(),
      annotations.end(),
      [&annotation_type_name](
        const java_bytecode_parse_treet::annotationt &annotation)
      {
        if(annotation.type.id() != ID_pointer)
          return false;
        typet type = annotation.type.subtype();
        if(type.id() == ID_symbol)
          return to_symbol_type(type).get_identifier() == annotation_type_name;
        return false;
      }) != annotations.end();
}

bool java_bytecode_convert_classt::is_overlay_class(const classt &c)
{
  return does_annotation_exist(c.annotations, ID_overlay_class);
}

bool java_bytecode_convert_classt::is_overlay_method(const methodt &method)
{
  return does_annotation_exist(method.annotations, ID_overlay_method);
}

bool java_bytecode_convert_class(
  const java_class_loadert::parse_tree_with_overlayst &parse_trees,
  symbol_tablet &symbol_table,
  message_handlert &message_handler,
  size_t max_array_length,
  method_bytecodet &method_bytecode,
  java_string_library_preprocesst &string_preprocess)
{
  java_bytecode_convert_classt java_bytecode_convert_class(
    symbol_table,
    message_handler,
    max_array_length,
    method_bytecode,
    string_preprocess);

  try
  {
    java_bytecode_convert_class(parse_trees);
    return false;
  }

  catch(int)
  {
  }

  catch(const char *e)
  {
    java_bytecode_convert_class.error() << e << messaget::eom;
  }

  catch(const std::string &e)
  {
    java_bytecode_convert_class.error() << e << messaget::eom;
  }

  return true;
}

/// For a given generic type parameter, check if there is a parameter in the
/// given vector of replacement parameters with a matching name. If yes,
/// replace the identifier of the parameter at hand by the identifier of
/// the matching parameter.
/// Example: if \p parameter has identifier `java::Outer$Inner::T` and
/// there is a replacement parameter with identifier `java::Outer::T`, the
/// identifier of \p parameter gets set to `java::Outer::T`.
/// \param parameter
/// \param replacement_parameters vector of generic parameters (only viable
///   ones, i.e., only those that can actually appear here such as generic
///   parameters of outer classes of the class specified by the prefix of \p
///   parameter identifier)
static void find_and_replace_parameter(
  java_generic_parametert &parameter,
  const std::vector<java_generic_parametert> &replacement_parameters)
{
  // get the name of the parameter, e.g., `T` from `java::Class::T`
  const std::string &parameter_full_name =
    id2string(parameter.type_variable_ref().get_identifier());
  const std::string &parameter_name =
    parameter_full_name.substr(parameter_full_name.rfind("::") + 2);

  // check if there is a replacement parameter with the same name
  const auto replacement_parameter_p = std::find_if(
    replacement_parameters.begin(),
    replacement_parameters.end(),
    [&parameter_name](const java_generic_parametert &replacement_param)
    {
      const std::string &replacement_parameter_full_name =
        id2string(replacement_param.type_variable().get_identifier());
      return parameter_name.compare(
               replacement_parameter_full_name.substr(
                 replacement_parameter_full_name.rfind("::") + 2)) == 0;
    });

  // if a replacement parameter was found, update the identifier
  if(replacement_parameter_p != replacement_parameters.end())
  {
    const std::string &replacement_parameter_full_name =
      id2string(replacement_parameter_p->type_variable().get_identifier());

    // the replacement parameter is a viable one, i.e., it comes from an outer
    // class
    PRECONDITION(
      parameter_full_name.substr(0, parameter_full_name.rfind("::"))
        .compare(
          replacement_parameter_full_name.substr(
            0, replacement_parameter_full_name.rfind("::"))) > 0);

    parameter.type_variable_ref().set_identifier(
      replacement_parameter_full_name);
  }
}

/// Recursively find all generic type parameters of a given type and replace
/// their identifiers if matching replacement parameter exist.
/// Example: class `Outer<T>` has an inner class `Inner` that has a field
/// `t` of type `Generic<T>`. This function ensures that the parameter points to
/// `java::Outer::T` instead of `java::Outer$Inner::T`.
/// \param type
/// \param replacement_parameters
static void find_and_replace_parameters(
  typet &type,
  const std::vector<java_generic_parametert> &replacement_parameters)
{
  if(is_java_generic_parameter(type))
  {
    find_and_replace_parameter(
      to_java_generic_parameter(type), replacement_parameters);
  }
  else if(is_java_generic_type(type))
  {
    java_generic_typet &generic_type = to_java_generic_type(type);
    std::vector<reference_typet> &arguments =
      generic_type.generic_type_arguments();
    for(auto &argument : arguments)
    {
      find_and_replace_parameters(argument, replacement_parameters);
    }
  }
  else if(is_java_generic_symbol_type(type))
  {
    java_generic_symbol_typet &generic_base = to_java_generic_symbol_type(type);
    std::vector<reference_typet> &gen_types = generic_base.generic_types();
    for(auto &gen_type : gen_types)
    {
      find_and_replace_parameters(gen_type, replacement_parameters);
    }
  }
}

/// Checks if the class is implicitly generic, i.e., it is an inner class of
/// any generic class. All uses of the implicit generic type parameters within
/// the inner class are updated to point to the type parameters of the
/// corresponding outer classes.
/// \param class_name
/// \param symbol_table
void mark_java_implicitly_generic_class_type(
  const irep_idt &class_name,
  symbol_tablet &symbol_table)
{
  const std::string qualified_class_name = "java::" + id2string(class_name);
  PRECONDITION(symbol_table.has_symbol(qualified_class_name));
  symbolt &class_symbol = symbol_table.get_writeable_ref(qualified_class_name);
  java_class_typet &class_type = to_java_class_type(class_symbol.type);

  // the class must be an inner non-static class, i.e., have a field this$*
  // TODO this should be simplified once static inner classes are marked
  // during parsing
  bool no_this_field = std::none_of(
    class_type.components().begin(),
    class_type.components().end(),
    [](const struct_union_typet::componentt &component)
    {
      return id2string(component.get_name()).substr(0, 5) == "this$";
    });
  if(no_this_field)
  {
    return;
  }

  // create a vector of all generic type parameters of all outer classes, in
  // the order from the outer-most inwards
  std::vector<java_generic_parametert> implicit_generic_type_parameters;
  std::string::size_type outer_class_delimiter =
    qualified_class_name.rfind("$");
  while(outer_class_delimiter != std::string::npos)
  {
    std::string outer_class_name =
      qualified_class_name.substr(0, outer_class_delimiter);
    if(symbol_table.has_symbol(outer_class_name))
    {
      const symbolt &outer_class_symbol =
        symbol_table.lookup_ref(outer_class_name);
      const java_class_typet &outer_class_type =
        to_java_class_type(outer_class_symbol.type);
      if(is_java_generic_class_type(outer_class_type))
      {
        const auto &outer_generic_type_parameters =
          to_java_generic_class_type(outer_class_type).generic_types();
        implicit_generic_type_parameters.insert(
          implicit_generic_type_parameters.begin(),
          outer_generic_type_parameters.begin(),
          outer_generic_type_parameters.end());
      }
      outer_class_delimiter = outer_class_name.rfind("$");
    }
    else
    {
      throw missing_outer_class_symbol_exceptiont(
        outer_class_name, qualified_class_name);
    }
  }

  // if there are any implicit generic type parameters, mark the class
  // implicitly generic and update identifiers of type parameters used in fields
  if(!implicit_generic_type_parameters.empty())
  {
    class_symbol.type = java_implicitly_generic_class_typet(
      class_type, implicit_generic_type_parameters);

    for(auto &field : class_type.components())
    {
      find_and_replace_parameters(
        field.type(), implicit_generic_type_parameters);
    }

    for(auto &base : class_type.bases())
    {
      find_and_replace_parameters(
        base.type(), implicit_generic_type_parameters);
    }
  }
}