// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

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

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

#include <string>

#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/compiler/java/helpers.h"
#include "google/protobuf/compiler/java/name_resolver.h"
#include "google/protobuf/compiler/java/options.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"

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

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

namespace {

const char* DefaultPackage(Options options) {
  return options.opensource_runtime ? "" : "com.google.protos";
}

bool IsReservedName(absl::string_view name) {
  static const auto& kReservedNames =
      *new absl::flat_hash_set<absl::string_view>({
          "abstract", "assert",     "boolean",  "break",     "byte",
          "case",     "catch",      "char",     "class",     "const",
          "continue", "default",    "do",       "double",    "else",
          "enum",     "extends",    "false",    "final",     "finally",
          "float",    "for",        "goto",     "if",        "implements",
          "import",   "instanceof", "int",      "interface", "java",
          "long",     "native",     "new",      "null",      "package",
          "private",  "protected",  "public",   "return",    "short",
          "static",   "strictfp",   "super",    "switch",    "synchronized",
          "this",     "throw",      "throws",   "transient", "true",
          "try",      "void",       "volatile", "while",
      });
  return kReservedNames.contains(name);
}

bool IsForbidden(absl::string_view field_name) {
  // Names that should be avoided (in UpperCamelCase format).
  // Using them will cause the compiler to generate accessors whose names
  // collide with methods defined in base classes.
  // Keep this list in sync with specialFieldNames in
  // java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java
  static const auto& kForbiddenNames =
      *new absl::flat_hash_set<absl::string_view>({
        // java.lang.Object:
          "Class",
          // com.google.protobuf.MessageLiteOrBuilder:
          "DefaultInstanceForType",
          // com.google.protobuf.MessageLite:
          "ParserForType",
          "SerializedSize",
          // com.google.protobuf.MessageOrBuilder:
          "AllFields",
          "DescriptorForType",
          "InitializationErrorString",
          "UnknownFields",
          // obsolete. kept for backwards compatibility of generated code
          "CachedSize",
      });
  return kForbiddenNames.contains(UnderscoresToCamelCase(field_name, true));
}

std::string FieldName(const FieldDescriptor* field) {
  std::string field_name;
  // Groups are hacky:  The name of the field is just the lower-cased name
  // of the group type.  In Java, though, we would like to retain the original
  // capitalization of the type name.
  if (internal::cpp::IsGroupLike(*field)) {
    field_name = field->message_type()->name();
  } else {
    field_name = field->name();
  }
  if (IsForbidden(field_name)) {
    // Append a trailing "#" to indicate that the name should be decorated to
    // avoid collision with other names.
    absl::StrAppend(&field_name, "#");
  }
  return field_name;
}

}  // namespace

std::string ClassName(const Descriptor* descriptor) {
  ClassNameResolver name_resolver;
  return name_resolver.GetClassName(descriptor, true);
}

std::string ClassName(const EnumDescriptor* descriptor) {
  ClassNameResolver name_resolver;
  return name_resolver.GetClassName(descriptor, true);
}

std::string ClassName(const ServiceDescriptor* descriptor) {
  ClassNameResolver name_resolver;
  return name_resolver.GetClassName(descriptor, true);
}

std::string ClassName(const FileDescriptor* descriptor) {
  ClassNameResolver name_resolver;
  return name_resolver.GetClassName(descriptor, true);
}

std::string FileJavaPackage(const FileDescriptor* file, bool immutable,
                            Options options) {
  std::string result;

  if (file->options().has_java_package()) {
    result = file->options().java_package();
  } else {
    result = DefaultPackage(options);
    if (!file->package().empty()) {
      if (!result.empty()) result += '.';
      result += file->package();
    }
  }

  return result;
}

std::string FileJavaPackage(const FileDescriptor* file, Options options) {
  return FileJavaPackage(file, true /* immutable */, options);
}

std::string JavaPackageDirectory(const FileDescriptor* file) {
  return JavaPackageToDir(FileJavaPackage(file));
}

std::string FileClassName(const FileDescriptor* file) {
  return FileClassName(file, /*immutable=*/true);
}

std::string CapitalizedFieldName(const FieldDescriptor* field) {
  return UnderscoresToCamelCase(FieldName(field), true);
}

std::string CapitalizedOneofName(const OneofDescriptor* oneof) {
  return UnderscoresToCamelCase(oneof->name(), true);
}

std::string UnderscoresToCamelCase(const FieldDescriptor* field) {
  return UnderscoresToCamelCase(FieldName(field), false);
}

std::string UnderscoresToCapitalizedCamelCase(const FieldDescriptor* field) {
  return UnderscoresToCamelCase(FieldName(field), true);
}

std::string UnderscoresToCamelCase(const MethodDescriptor* method) {
  return UnderscoresToCamelCase(method->name(), false);
}

std::string UnderscoresToCamelCaseCheckReserved(const FieldDescriptor* field) {
  std::string name = UnderscoresToCamelCase(field);
  if (IsReservedName(name)) {
    absl::StrAppend(&name, "_");
  }
  return name;
}


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

#include "google/protobuf/port_undef.inc"
