// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Author: kenton@google.com (Kenton Varda)
//  Based on original Protocol Buffers design by
//  Sanjay Ghemawat, Jeff Dean, and others.

#include "google/protobuf/compiler/java/helpers.h"

#include <algorithm>
#include <cstdint>
#include <limits>
#include <vector>

#include "y_absl/container/flat_hash_set.h"
#include "y_absl/log/absl_check.h"
#include "y_absl/log/absl_log.h"
#include "y_absl/strings/ascii.h"
#include "y_absl/strings/escaping.h"
#include "y_absl/strings/str_cat.h"
#include "y_absl/strings/str_format.h"
#include "y_absl/strings/str_replace.h"
#include "y_absl/strings/str_split.h"
#include "y_absl/strings/string_view.h"
#include "y_absl/strings/substitute.h"
#include "google/protobuf/compiler/java/name_resolver.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/io/strtod.h"
#include "google/protobuf/wire_format.h"

// Must be last.
#include "google/protobuf/port_def.inc"

namespace google {
namespace protobuf {
namespace compiler {
namespace java {

using internal::WireFormat;
using internal::WireFormatLite;

const char kThickSeparator[] =
    "// ===================================================================\n";
const char kThinSeparator[] =
    "// -------------------------------------------------------------------\n";

void PrintGeneratedAnnotation(io::Printer* printer, char delimiter,
                              y_absl::string_view annotation_file,
                              Options options) {
  if (annotation_file.empty()) {
    return;
  }
  TProtoStringType ptemplate =
      "@javax.annotation.Generated(value=\"protoc\", comments=\"annotations:";
  ptemplate.push_back(delimiter);
  ptemplate.append("annotation_file");
  ptemplate.push_back(delimiter);
  ptemplate.append("\")\n");
  printer->Print(ptemplate.c_str(), "annotation_file", annotation_file);
}

void PrintEnumVerifierLogic(
    io::Printer* printer, const FieldDescriptor* descriptor,
    const y_absl::flat_hash_map<y_absl::string_view, TProtoStringType>& variables,
    y_absl::string_view var_name, y_absl::string_view terminating_string,
    bool enforce_lite) {
  TProtoStringType enum_verifier_string =
      enforce_lite ? y_absl::StrCat(var_name, ".internalGetVerifier()")
                   : y_absl::StrCat(
                         "new com.google.protobuf.Internal.EnumVerifier() {\n"
                         "        @java.lang.Override\n"
                         "        public boolean isInRange(int number) {\n"
                         "          return ",
                         var_name,
                         ".forNumber(number) != null;\n"
                         "        }\n"
                         "      }");
  printer->Print(variables,
                 y_absl::StrCat(enum_verifier_string, terminating_string));
}

TProtoStringType UnderscoresToCamelCase(y_absl::string_view input,
                                   bool cap_next_letter) {
  Y_ABSL_CHECK(!input.empty());
  TProtoStringType result;
  // Note:  I distrust ctype.h due to locales.
  for (int i = 0; i < input.size(); i++) {
    if ('a' <= input[i] && input[i] <= 'z') {
      if (cap_next_letter) {
        result += input[i] + ('A' - 'a');
      } else {
        result += input[i];
      }
      cap_next_letter = false;
    } else if ('A' <= input[i] && input[i] <= 'Z') {
      if (i == 0 && !cap_next_letter) {
        // Force first letter to lower-case unless explicitly told to
        // capitalize it.
        result += input[i] + ('a' - 'A');
      } else {
        // Capital letters after the first are left as-is.
        result += input[i];
      }
      cap_next_letter = false;
    } else if ('0' <= input[i] && input[i] <= '9') {
      result += input[i];
      cap_next_letter = true;
    } else {
      cap_next_letter = true;
    }
  }
  // Add a trailing "_" if the name should be altered.
  if (input[input.size() - 1] == '#') {
    result += '_';
  }
  return result;
}

TProtoStringType ToCamelCase(y_absl::string_view input, bool lower_first) {
  bool capitalize_next = !lower_first;
  TProtoStringType result;
  result.reserve(input.size());

  for (char i : input) {
    if (i == '_') {
      capitalize_next = true;
    } else if (capitalize_next) {
      result.push_back(y_absl::ascii_toupper(i));
      capitalize_next = false;
    } else {
      result.push_back(i);
    }
  }

  // Lower-case the first letter.
  if (lower_first && !result.empty()) {
    result[0] = y_absl::ascii_tolower(result[0]);
  }

  return result;
}

// Names that should be avoided as field names in Kotlin.
// All Kotlin hard keywords are in this list.
bool IsForbiddenKotlin(y_absl::string_view field_name) {
  static const auto& kKotlinForbiddenNames =
      *new y_absl::flat_hash_set<y_absl::string_view>({
          "as",      "as?",       "break",  "class", "continue", "do",
          "else",    "false",     "for",    "fun",   "if",       "in",
          "!in",     "interface", "is",     "!is",   "null",     "object",
          "package", "return",    "super",  "this",  "throw",    "true",
          "try",     "typealias", "typeof", "val",   "var",      "when",
          "while",
      });

  return kKotlinForbiddenNames.contains(field_name);
}

TProtoStringType EscapeKotlinKeywords(TProtoStringType name) {
  std::vector<TProtoStringType> escaped_packages;
  std::vector<TProtoStringType> packages = y_absl::StrSplit(name, ".");  // NOLINT
  for (y_absl::string_view package : packages) {
    if (IsForbiddenKotlin(package)) {
      escaped_packages.push_back(y_absl::StrCat("`", package, "`"));
    } else {
      escaped_packages.emplace_back(package);
    }
  }
  return y_absl::StrJoin(escaped_packages, ".");
}

TProtoStringType UniqueFileScopeIdentifier(const Descriptor* descriptor) {
  return y_absl::StrCat(
      "static_", y_absl::StrReplaceAll(descriptor->full_name(), {{".", "_"}}));
}

TProtoStringType CamelCaseFieldName(const FieldDescriptor* field) {
  TProtoStringType fieldName = UnderscoresToCamelCase(field);
  if ('0' <= fieldName[0] && fieldName[0] <= '9') {
    return y_absl::StrCat("_", fieldName);
  }
  return fieldName;
}

TProtoStringType FileClassName(const FileDescriptor* file, bool immutable) {
  return ClassNameResolver().GetFileClassName(file, immutable);
}

TProtoStringType JavaPackageToDir(TProtoStringType package_name) {
  TProtoStringType package_dir = y_absl::StrReplaceAll(package_name, {{".", "/"}});
  if (!package_dir.empty()) y_absl::StrAppend(&package_dir, "/");
  return package_dir;
}

TProtoStringType ExtraMessageInterfaces(const Descriptor* descriptor) {
  return y_absl::StrCat("// @@protoc_insertion_point(message_implements:",
                      descriptor->full_name(), ")");
}


TProtoStringType ExtraBuilderInterfaces(const Descriptor* descriptor) {
  return y_absl::StrCat("// @@protoc_insertion_point(builder_implements:",
                      descriptor->full_name(), ")");
}

TProtoStringType ExtraMessageOrBuilderInterfaces(const Descriptor* descriptor) {
  return y_absl::StrCat("// @@protoc_insertion_point(interface_extends:",
                      descriptor->full_name(), ")");
}

TProtoStringType FieldConstantName(const FieldDescriptor* field) {
  TProtoStringType name = y_absl::StrCat(field->name(), "_FIELD_NUMBER");
  y_absl::AsciiStrToUpper(&name);
  return name;
}

FieldDescriptor::Type GetType(const FieldDescriptor* field) {
  return field->type();
}

JavaType GetJavaType(const FieldDescriptor* field) {
  switch (GetType(field)) {
    case FieldDescriptor::TYPE_INT32:
    case FieldDescriptor::TYPE_UINT32:
    case FieldDescriptor::TYPE_SINT32:
    case FieldDescriptor::TYPE_FIXED32:
    case FieldDescriptor::TYPE_SFIXED32:
      return JAVATYPE_INT;

    case FieldDescriptor::TYPE_INT64:
    case FieldDescriptor::TYPE_UINT64:
    case FieldDescriptor::TYPE_SINT64:
    case FieldDescriptor::TYPE_FIXED64:
    case FieldDescriptor::TYPE_SFIXED64:
      return JAVATYPE_LONG;

    case FieldDescriptor::TYPE_FLOAT:
      return JAVATYPE_FLOAT;

    case FieldDescriptor::TYPE_DOUBLE:
      return JAVATYPE_DOUBLE;

    case FieldDescriptor::TYPE_BOOL:
      return JAVATYPE_BOOLEAN;

    case FieldDescriptor::TYPE_STRING:
      return JAVATYPE_STRING;

    case FieldDescriptor::TYPE_BYTES:
      return JAVATYPE_BYTES;

    case FieldDescriptor::TYPE_ENUM:
      return JAVATYPE_ENUM;

    case FieldDescriptor::TYPE_GROUP:
    case FieldDescriptor::TYPE_MESSAGE:
      return JAVATYPE_MESSAGE;

      // No default because we want the compiler to complain if any new
      // types are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return JAVATYPE_INT;
}

y_absl::string_view PrimitiveTypeName(JavaType type) {
  switch (type) {
    case JAVATYPE_INT:
      return "int";
    case JAVATYPE_LONG:
      return "long";
    case JAVATYPE_FLOAT:
      return "float";
    case JAVATYPE_DOUBLE:
      return "double";
    case JAVATYPE_BOOLEAN:
      return "boolean";
    case JAVATYPE_STRING:
      return "java.lang.String";
    case JAVATYPE_BYTES:
      return "com.google.protobuf.ByteString";
    case JAVATYPE_ENUM:
      return {};
    case JAVATYPE_MESSAGE:
      return {};

      // No default because we want the compiler to complain if any new
      // JavaTypes are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return {};
}

y_absl::string_view PrimitiveTypeName(const FieldDescriptor* descriptor) {
  return PrimitiveTypeName(GetJavaType(descriptor));
}

y_absl::string_view BoxedPrimitiveTypeName(JavaType type) {
  switch (type) {
    case JAVATYPE_INT:
      return "java.lang.Integer";
    case JAVATYPE_LONG:
      return "java.lang.Long";
    case JAVATYPE_FLOAT:
      return "java.lang.Float";
    case JAVATYPE_DOUBLE:
      return "java.lang.Double";
    case JAVATYPE_BOOLEAN:
      return "java.lang.Boolean";
    case JAVATYPE_STRING:
      return "java.lang.String";
    case JAVATYPE_BYTES:
      return "com.google.protobuf.ByteString";
    case JAVATYPE_ENUM:
      return {};
    case JAVATYPE_MESSAGE:
      return {};

      // No default because we want the compiler to complain if any new
      // JavaTypes are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return {};
}

y_absl::string_view BoxedPrimitiveTypeName(const FieldDescriptor* descriptor) {
  return BoxedPrimitiveTypeName(GetJavaType(descriptor));
}

y_absl::string_view KotlinTypeName(JavaType type) {
  switch (type) {
    case JAVATYPE_INT:
      return "kotlin.Int";
    case JAVATYPE_LONG:
      return "kotlin.Long";
    case JAVATYPE_FLOAT:
      return "kotlin.Float";
    case JAVATYPE_DOUBLE:
      return "kotlin.Double";
    case JAVATYPE_BOOLEAN:
      return "kotlin.Boolean";
    case JAVATYPE_STRING:
      return "kotlin.String";
    case JAVATYPE_BYTES:
      return "com.google.protobuf.ByteString";
    case JAVATYPE_ENUM:
      return {};
    case JAVATYPE_MESSAGE:
      return {};

      // No default because we want the compiler to complain if any new
      // JavaTypes are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return {};
}

TProtoStringType GetOneofStoredType(const FieldDescriptor* field) {
  const JavaType javaType = GetJavaType(field);
  switch (javaType) {
    case JAVATYPE_ENUM:
      return "java.lang.Integer";
    case JAVATYPE_MESSAGE:
      return ClassNameResolver().GetClassName(field->message_type(), true);
    default:
      return TProtoStringType(BoxedPrimitiveTypeName(javaType));
  }
}

y_absl::string_view FieldTypeName(FieldDescriptor::Type field_type) {
  switch (field_type) {
    case FieldDescriptor::TYPE_INT32:
      return "INT32";
    case FieldDescriptor::TYPE_UINT32:
      return "UINT32";
    case FieldDescriptor::TYPE_SINT32:
      return "SINT32";
    case FieldDescriptor::TYPE_FIXED32:
      return "FIXED32";
    case FieldDescriptor::TYPE_SFIXED32:
      return "SFIXED32";
    case FieldDescriptor::TYPE_INT64:
      return "INT64";
    case FieldDescriptor::TYPE_UINT64:
      return "UINT64";
    case FieldDescriptor::TYPE_SINT64:
      return "SINT64";
    case FieldDescriptor::TYPE_FIXED64:
      return "FIXED64";
    case FieldDescriptor::TYPE_SFIXED64:
      return "SFIXED64";
    case FieldDescriptor::TYPE_FLOAT:
      return "FLOAT";
    case FieldDescriptor::TYPE_DOUBLE:
      return "DOUBLE";
    case FieldDescriptor::TYPE_BOOL:
      return "BOOL";
    case FieldDescriptor::TYPE_STRING:
      return "STRING";
    case FieldDescriptor::TYPE_BYTES:
      return "BYTES";
    case FieldDescriptor::TYPE_ENUM:
      return "ENUM";
    case FieldDescriptor::TYPE_GROUP:
      return "GROUP";
    case FieldDescriptor::TYPE_MESSAGE:
      return "MESSAGE";

      // No default because we want the compiler to complain if any new
      // types are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return {};
}

bool AllAscii(y_absl::string_view text) {
  for (int i = 0; i < text.size(); i++) {
    if ((text[i] & 0x80) != 0) {
      return false;
    }
  }
  return true;
}

TProtoStringType DefaultValue(const FieldDescriptor* field, bool immutable,
                         ClassNameResolver* name_resolver, Options options) {
  // Switch on CppType since we need to know which default_value_* method
  // of FieldDescriptor to call.
  switch (field->cpp_type()) {
    case FieldDescriptor::CPPTYPE_INT32:
      return y_absl::StrCat(field->default_value_int32());
    case FieldDescriptor::CPPTYPE_UINT32:
      // Need to print as a signed int since Java has no unsigned.
      return y_absl::StrCat(static_cast<arc_i32>(field->default_value_uint32()));
    case FieldDescriptor::CPPTYPE_INT64:
      return y_absl::StrCat(field->default_value_int64(), "L");
    case FieldDescriptor::CPPTYPE_UINT64:
      return y_absl::StrCat(static_cast<arc_i64>(field->default_value_uint64())) +
             "L";
    case FieldDescriptor::CPPTYPE_DOUBLE: {
      double value = field->default_value_double();
      if (value == std::numeric_limits<double>::infinity()) {
        return "Double.POSITIVE_INFINITY";
      } else if (value == -std::numeric_limits<double>::infinity()) {
        return "Double.NEGATIVE_INFINITY";
      } else if (value != value) {
        return "Double.NaN";
      } else {
        return y_absl::StrCat(io::SimpleDtoa(value), "D");
      }
    }
    case FieldDescriptor::CPPTYPE_FLOAT: {
      float value = field->default_value_float();
      if (value == std::numeric_limits<float>::infinity()) {
        return "Float.POSITIVE_INFINITY";
      } else if (value == -std::numeric_limits<float>::infinity()) {
        return "Float.NEGATIVE_INFINITY";
      } else if (value != value) {
        return "Float.NaN";
      } else {
        return y_absl::StrCat(io::SimpleFtoa(value), "F");
      }
    }
    case FieldDescriptor::CPPTYPE_BOOL:
      return field->default_value_bool() ? "true" : "false";
    case FieldDescriptor::CPPTYPE_STRING:
      if (GetType(field) == FieldDescriptor::TYPE_BYTES) {
        if (field->has_default_value()) {
          // See comments in Internal.java for gory details.
          return y_absl::Substitute(
              "com.google.protobuf.Internal.bytesDefaultValue(\"$0\")",
              y_absl::CEscape(field->default_value_string()));
        } else {
          return "com.google.protobuf.ByteString.EMPTY";
        }
      } else {
        if (AllAscii(field->default_value_string())) {
          // All chars are ASCII.  In this case CEscape() works fine.
          return y_absl::StrCat(
              "\"", y_absl::CEscape(field->default_value_string()), "\"");
        } else {
          // See comments in Internal.java for gory details.
          return y_absl::Substitute(
              "com.google.protobuf.Internal.stringDefaultValue(\"$0\")",
              y_absl::CEscape(field->default_value_string()));
        }
      }

    case FieldDescriptor::CPPTYPE_ENUM:
      return y_absl::StrCat(
          name_resolver->GetClassName(field->enum_type(), immutable), ".",
          field->default_value_enum()->name());

    case FieldDescriptor::CPPTYPE_MESSAGE:
      return y_absl::StrCat(
          name_resolver->GetClassName(field->message_type(), immutable),
          ".getDefaultInstance()");

      // No default because we want the compiler to complain if any new
      // types are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return "";
}

bool IsDefaultValueJavaDefault(const FieldDescriptor* field) {
  // Switch on CppType since we need to know which default_value_* method
  // of FieldDescriptor to call.
  switch (field->cpp_type()) {
    case FieldDescriptor::CPPTYPE_INT32:
      return field->default_value_int32() == 0;
    case FieldDescriptor::CPPTYPE_UINT32:
      return field->default_value_uint32() == 0;
    case FieldDescriptor::CPPTYPE_INT64:
      return field->default_value_int64() == 0L;
    case FieldDescriptor::CPPTYPE_UINT64:
      return field->default_value_uint64() == 0L;
    case FieldDescriptor::CPPTYPE_DOUBLE:
      return field->default_value_double() == 0.0;
    case FieldDescriptor::CPPTYPE_FLOAT:
      return field->default_value_float() == 0.0;
    case FieldDescriptor::CPPTYPE_BOOL:
      return field->default_value_bool() == false;
    case FieldDescriptor::CPPTYPE_ENUM:
      return field->default_value_enum()->number() == 0;
    case FieldDescriptor::CPPTYPE_STRING:
    case FieldDescriptor::CPPTYPE_MESSAGE:
      return false;

      // No default because we want the compiler to complain if any new
      // types are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return false;
}

bool IsByteStringWithCustomDefaultValue(const FieldDescriptor* field) {
  return GetJavaType(field) == JAVATYPE_BYTES &&
         field->default_value_string() != "";
}

constexpr y_absl::string_view bit_masks[] = {
    "0x00000001", "0x00000002", "0x00000004", "0x00000008",
    "0x00000010", "0x00000020", "0x00000040", "0x00000080",

    "0x00000100", "0x00000200", "0x00000400", "0x00000800",
    "0x00001000", "0x00002000", "0x00004000", "0x00008000",

    "0x00010000", "0x00020000", "0x00040000", "0x00080000",
    "0x00100000", "0x00200000", "0x00400000", "0x00800000",

    "0x01000000", "0x02000000", "0x04000000", "0x08000000",
    "0x10000000", "0x20000000", "0x40000000", "0x80000000",
};

TProtoStringType GetBitFieldName(int index) {
  return y_absl::StrCat("bitField", index, "_");
}

TProtoStringType GetBitFieldNameForBit(int bitIndex) {
  return GetBitFieldName(bitIndex / 32);
}

namespace {

TProtoStringType GenerateGetBitInternal(y_absl::string_view prefix, int bitIndex) {
  TProtoStringType varName = y_absl::StrCat(prefix, GetBitFieldNameForBit(bitIndex));
  int bitInVarIndex = bitIndex % 32;

  return y_absl::StrCat("((", varName, " & ", bit_masks[bitInVarIndex],
                      ") != 0)");
}

TProtoStringType GenerateSetBitInternal(y_absl::string_view prefix, int bitIndex) {
  TProtoStringType varName = y_absl::StrCat(prefix, GetBitFieldNameForBit(bitIndex));
  int bitInVarIndex = bitIndex % 32;

  return y_absl::StrCat(varName, " |= ", bit_masks[bitInVarIndex]);
}

}  // namespace

TProtoStringType GenerateGetBit(int bitIndex) {
  return GenerateGetBitInternal("", bitIndex);
}

TProtoStringType GenerateSetBit(int bitIndex) {
  return GenerateSetBitInternal("", bitIndex);
}

TProtoStringType GenerateClearBit(int bitIndex) {
  TProtoStringType varName = GetBitFieldNameForBit(bitIndex);
  int bitInVarIndex = bitIndex % 32;

  return y_absl::StrCat(varName, " = (", varName, " & ~",
                      bit_masks[bitInVarIndex], ")");
}

TProtoStringType GenerateGetBitFromLocal(int bitIndex) {
  return GenerateGetBitInternal("from_", bitIndex);
}

TProtoStringType GenerateSetBitToLocal(int bitIndex) {
  return GenerateSetBitInternal("to_", bitIndex);
}

TProtoStringType GenerateGetBitMutableLocal(int bitIndex) {
  return GenerateGetBitInternal("mutable_", bitIndex);
}

TProtoStringType GenerateSetBitMutableLocal(int bitIndex) {
  return GenerateSetBitInternal("mutable_", bitIndex);
}

bool IsReferenceType(JavaType type) {
  switch (type) {
    case JAVATYPE_INT:
      return false;
    case JAVATYPE_LONG:
      return false;
    case JAVATYPE_FLOAT:
      return false;
    case JAVATYPE_DOUBLE:
      return false;
    case JAVATYPE_BOOLEAN:
      return false;
    case JAVATYPE_STRING:
      return true;
    case JAVATYPE_BYTES:
      return true;
    case JAVATYPE_ENUM:
      return true;
    case JAVATYPE_MESSAGE:
      return true;

      // No default because we want the compiler to complain if any new
      // JavaTypes are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return false;
}

y_absl::string_view GetCapitalizedType(const FieldDescriptor* field,
                                     bool immutable, Options options) {
  switch (GetType(field)) {
    case FieldDescriptor::TYPE_INT32:
      return "Int32";
    case FieldDescriptor::TYPE_UINT32:
      return "UInt32";
    case FieldDescriptor::TYPE_SINT32:
      return "SInt32";
    case FieldDescriptor::TYPE_FIXED32:
      return "Fixed32";
    case FieldDescriptor::TYPE_SFIXED32:
      return "SFixed32";
    case FieldDescriptor::TYPE_INT64:
      return "Int64";
    case FieldDescriptor::TYPE_UINT64:
      return "UInt64";
    case FieldDescriptor::TYPE_SINT64:
      return "SInt64";
    case FieldDescriptor::TYPE_FIXED64:
      return "Fixed64";
    case FieldDescriptor::TYPE_SFIXED64:
      return "SFixed64";
    case FieldDescriptor::TYPE_FLOAT:
      return "Float";
    case FieldDescriptor::TYPE_DOUBLE:
      return "Double";
    case FieldDescriptor::TYPE_BOOL:
      return "Bool";
    case FieldDescriptor::TYPE_STRING:
      return "String";
    case FieldDescriptor::TYPE_BYTES: {
      return "Bytes";
    }
    case FieldDescriptor::TYPE_ENUM:
      return "Enum";
    case FieldDescriptor::TYPE_GROUP:
      return "Group";
    case FieldDescriptor::TYPE_MESSAGE:
      return "Message";

      // No default because we want the compiler to complain if any new
      // types are added.
  }

  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return {};
}

// For encodings with fixed sizes, returns that size in bytes.  Otherwise
// returns -1.
int FixedSize(FieldDescriptor::Type type) {
  switch (type) {
    case FieldDescriptor::TYPE_INT32:
      return -1;
    case FieldDescriptor::TYPE_INT64:
      return -1;
    case FieldDescriptor::TYPE_UINT32:
      return -1;
    case FieldDescriptor::TYPE_UINT64:
      return -1;
    case FieldDescriptor::TYPE_SINT32:
      return -1;
    case FieldDescriptor::TYPE_SINT64:
      return -1;
    case FieldDescriptor::TYPE_FIXED32:
      return WireFormatLite::kFixed32Size;
    case FieldDescriptor::TYPE_FIXED64:
      return WireFormatLite::kFixed64Size;
    case FieldDescriptor::TYPE_SFIXED32:
      return WireFormatLite::kSFixed32Size;
    case FieldDescriptor::TYPE_SFIXED64:
      return WireFormatLite::kSFixed64Size;
    case FieldDescriptor::TYPE_FLOAT:
      return WireFormatLite::kFloatSize;
    case FieldDescriptor::TYPE_DOUBLE:
      return WireFormatLite::kDoubleSize;

    case FieldDescriptor::TYPE_BOOL:
      return WireFormatLite::kBoolSize;
    case FieldDescriptor::TYPE_ENUM:
      return -1;

    case FieldDescriptor::TYPE_STRING:
      return -1;
    case FieldDescriptor::TYPE_BYTES:
      return -1;
    case FieldDescriptor::TYPE_GROUP:
      return -1;
    case FieldDescriptor::TYPE_MESSAGE:
      return -1;

      // No default because we want the compiler to complain if any new
      // types are added.
  }
  Y_ABSL_LOG(FATAL) << "Can't get here.";
  return -1;
}

// Sort the fields of the given Descriptor by number into a new[]'d array
// and return it. The caller should delete the returned array.
const FieldDescriptor** SortFieldsByNumber(const Descriptor* descriptor) {
  const FieldDescriptor** fields =
      new const FieldDescriptor*[descriptor->field_count()];
  for (int i = 0; i < descriptor->field_count(); i++) {
    fields[i] = descriptor->field(i);
  }
  std::sort(fields, fields + descriptor->field_count(),
            FieldOrderingByNumber());
  return fields;
}

// Returns true if the message type has any required fields.  If it doesn't,
// we can optimize out calls to its isInitialized() method.
//
// already_seen is used to avoid checking the same type multiple times
// (and also to protect against recursion).
bool HasRequiredFields(const Descriptor* type,
                       y_absl::flat_hash_set<const Descriptor*>* already_seen) {
  if (already_seen->count(type) > 0) {
    // The type is already in cache.  This means that either:
    // a. The type has no required fields.
    // b. We are in the midst of checking if the type has required fields,
    //    somewhere up the stack.  In this case, we know that if the type
    //    has any required fields, they'll be found when we return to it,
    //    and the whole call to HasRequiredFields() will return true.
    //    Therefore, we don't have to check if this type has required fields
    //    here.
    return false;
  }
  already_seen->insert(type);

  // If the type has extensions, an extension with message type could contain
  // required fields, so we have to be conservative and assume such an
  // extension exists.
  if (type->extension_range_count() > 0) return true;

  for (int i = 0; i < type->field_count(); i++) {
    const FieldDescriptor* field = type->field(i);
    if (field->is_required()) {
      return true;
    }
    if (GetJavaType(field) == JAVATYPE_MESSAGE) {
      if (HasRequiredFields(field->message_type(), already_seen)) {
        return true;
      }
    }
  }

  return false;
}

bool HasRequiredFields(const Descriptor* type) {
  y_absl::flat_hash_set<const Descriptor*> already_seen;
  return HasRequiredFields(type, &already_seen);
}

bool HasRepeatedFields(const Descriptor* descriptor) {
  for (int i = 0; i < descriptor->field_count(); ++i) {
    const FieldDescriptor* field = descriptor->field(i);
    if (field->is_repeated()) {
      return true;
    }
  }
  return false;
}

// Encode an unsigned 32-bit value into a sequence of UTF-16 characters.
//
// If the value is in [0x0000, 0xD7FF], we encode it with a single character
// with the same numeric value.
//
// If the value is larger than 0xD7FF, we encode its lowest 13 bits into a
// character in the range [0xE000, 0xFFFF] by combining these 13 bits with
// 0xE000 using logic-or. Then we shift the value to the right by 13 bits, and
// encode the remaining value by repeating this same process until we get to
// a value in [0x0000, 0xD7FF] where we will encode it using a character with
// the same numeric value.
//
// Note that we only use code points in [0x0000, 0xD7FF] and [0xE000, 0xFFFF].
// There will be no surrogate pairs in the encoded character sequence.
void WriteUInt32ToUtf16CharSequence(arc_ui32 number,
                                    std::vector<uint16_t>* output) {
  // For values in [0x0000, 0xD7FF], only use one char to encode it.
  if (number < 0xD800) {
    output->push_back(static_cast<uint16_t>(number));
    return;
  }
  // Encode into multiple chars. All except the last char will be in the range
  // [0xE000, 0xFFFF], and the last char will be in the range [0x0000, 0xD7FF].
  // Note that we don't use any value in range [0xD800, 0xDFFF] because they
  // have to come in pairs and the encoding is just more space-efficient w/o
  // them.
  while (number >= 0xD800) {
    // [0xE000, 0xFFFF] can represent 13 bits of info.
    output->push_back(static_cast<uint16_t>(0xE000 | (number & 0x1FFF)));
    number >>= 13;
  }
  output->push_back(static_cast<uint16_t>(number));
}

int GetExperimentalJavaFieldTypeForSingular(const FieldDescriptor* field) {
  // j/c/g/protobuf/FieldType.java lists field types in a slightly different
  // order from FieldDescriptor::Type so we can't do a simple cast.
  //
  // TODO(xiaofeng): Make j/c/g/protobuf/FieldType.java follow the same order.
  int result = field->type();
  if (result == FieldDescriptor::TYPE_GROUP) {
    return 17;
  } else if (result < FieldDescriptor::TYPE_GROUP) {
    return result - 1;
  } else {
    return result - 2;
  }
}

int GetExperimentalJavaFieldTypeForRepeated(const FieldDescriptor* field) {
  if (field->type() == FieldDescriptor::TYPE_GROUP) {
    return 49;
  } else {
    return GetExperimentalJavaFieldTypeForSingular(field) + 18;
  }
}

int GetExperimentalJavaFieldTypeForPacked(const FieldDescriptor* field) {
  int result = field->type();
  if (result < FieldDescriptor::TYPE_STRING) {
    return result + 34;
  } else if (result > FieldDescriptor::TYPE_BYTES) {
    return result + 30;
  } else {
    Y_ABSL_LOG(FATAL) << field->full_name() << " can't be packed.";
    return 0;
  }
}

int GetExperimentalJavaFieldType(const FieldDescriptor* field) {
  static const int kMapFieldType = 50;
  static const int kOneofFieldTypeOffset = 51;
  static const int kRequiredBit = 0x100;
  static const int kUtf8CheckBit = 0x200;
  static const int kCheckInitialized = 0x400;
  static const int kMapWithProto2EnumValue = 0x800;
  static const int kHasHasBit = 0x1000;
  int extra_bits = field->is_required() ? kRequiredBit : 0;
  if (field->type() == FieldDescriptor::TYPE_STRING && CheckUtf8(field)) {
    extra_bits |= kUtf8CheckBit;
  }
  if (field->is_required() || (GetJavaType(field) == JAVATYPE_MESSAGE &&
                               HasRequiredFields(field->message_type()))) {
    extra_bits |= kCheckInitialized;
  }
  if (HasHasbit(field)) {
    extra_bits |= kHasHasBit;
  }

  if (field->is_map()) {
    if (!SupportUnknownEnumValue(field)) {
      const FieldDescriptor* value = field->message_type()->map_value();
      if (GetJavaType(value) == JAVATYPE_ENUM) {
        extra_bits |= kMapWithProto2EnumValue;
      }
    }
    return kMapFieldType | extra_bits;
  } else if (field->is_packed()) {
    return GetExperimentalJavaFieldTypeForPacked(field);
  } else if (field->is_repeated()) {
    return GetExperimentalJavaFieldTypeForRepeated(field) | extra_bits;
  } else if (IsRealOneof(field)) {
    return (GetExperimentalJavaFieldTypeForSingular(field) +
            kOneofFieldTypeOffset) |
           extra_bits;
  } else {
    return GetExperimentalJavaFieldTypeForSingular(field) | extra_bits;
  }
}

// Escape a UTF-16 character to be embedded in a Java string.
void EscapeUtf16ToString(uint16_t code, TProtoStringType* output) {
  if (code == '\t') {
    output->append("\\t");
  } else if (code == '\b') {
    output->append("\\b");
  } else if (code == '\n') {
    output->append("\\n");
  } else if (code == '\r') {
    output->append("\\r");
  } else if (code == '\f') {
    output->append("\\f");
  } else if (code == '\'') {
    output->append("\\'");
  } else if (code == '\"') {
    output->append("\\\"");
  } else if (code == '\\') {
    output->append("\\\\");
  } else if (code >= 0x20 && code <= 0x7f) {
    output->push_back(static_cast<char>(code));
  } else {
    output->append(y_absl::StrFormat("\\u%04x", code));
  }
}

}  // namespace java
}  // namespace compiler
}  // namespace protobuf
}  // namespace google

#include "google/protobuf/port_undef.inc"