diff options
Diffstat (limited to 'src/google/protobuf/compiler/csharp/csharp_helpers.cc')
-rw-r--r-- | src/google/protobuf/compiler/csharp/csharp_helpers.cc | 215 |
1 files changed, 161 insertions, 54 deletions
diff --git a/src/google/protobuf/compiler/csharp/csharp_helpers.cc b/src/google/protobuf/compiler/csharp/csharp_helpers.cc index 333b4912..6c154c5a 100644 --- a/src/google/protobuf/compiler/csharp/csharp_helpers.cc +++ b/src/google/protobuf/compiler/csharp/csharp_helpers.cc @@ -48,6 +48,7 @@ #include <google/protobuf/compiler/csharp/csharp_enum_field.h> #include <google/protobuf/compiler/csharp/csharp_map_field.h> #include <google/protobuf/compiler/csharp/csharp_message_field.h> +#include <google/protobuf/compiler/csharp/csharp_options.h> #include <google/protobuf/compiler/csharp/csharp_primitive_field.h> #include <google/protobuf/compiler/csharp/csharp_repeated_enum_field.h> #include <google/protobuf/compiler/csharp/csharp_repeated_message_field.h> @@ -117,43 +118,19 @@ std::string GetFileNamespace(const FileDescriptor* descriptor) { return UnderscoresToCamelCase(descriptor->package(), true, true); } -std::string GetUmbrellaClassUnqualifiedName(const FileDescriptor* descriptor) { - // We manually rename Descriptor to DescriptorProtoFile to avoid collisions with - // the static Descriptor property. It would be nice to be able to do this with an - // option, but it would be rarely used. - if (IsDescriptorProto(descriptor)) { - return "DescriptorProtoFile"; - } - // umbrella_classname can no longer be set using message option. - std::string proto_file = descriptor->name(); - int lastslash = proto_file.find_last_of("/"); - std::string base = proto_file.substr(lastslash + 1); - return UnderscoresToPascalCase(StripDotProto(base)); +// Returns the Pascal-cased last part of the proto file. For example, +// input of "google/protobuf/foo_bar.proto" would result in "FooBar". +std::string GetFileNameBase(const FileDescriptor* descriptor) { + std::string proto_file = descriptor->name(); + int lastslash = proto_file.find_last_of("/"); + std::string base = proto_file.substr(lastslash + 1); + return UnderscoresToPascalCase(StripDotProto(base)); } -std::string GetUmbrellaClassNestedNamespace(const FileDescriptor* descriptor) { - // TODO(jtattermusch): reintroduce csharp_umbrella_namespace option - bool collision = false; - std::string umbrella_classname = GetUmbrellaClassUnqualifiedName(descriptor); - for(int i = 0; i < descriptor->message_type_count(); i++) { - if (descriptor->message_type(i)->name() == umbrella_classname) { - collision = true; - break; - } - } - for (int i = 0; i < descriptor->service_count(); i++) { - if (descriptor->service(i)->name() == umbrella_classname) { - collision = true; - break; - } - } - for (int i = 0; i < descriptor->enum_type_count(); i++) { - if (descriptor->enum_type(i)->name() == umbrella_classname) { - collision = true; - break; - } - } - return collision ? "Proto" : ""; +std::string GetReflectionClassUnqualifiedName(const FileDescriptor* descriptor) { + // TODO: Detect collisions with existing messages, + // and append an underscore if necessary. + return GetFileNameBase(descriptor) + "Reflection"; } // TODO(jtattermusch): can we reuse a utility function? @@ -201,6 +178,104 @@ std::string UnderscoresToPascalCase(const std::string& input) { return UnderscoresToCamelCase(input, true); } +// Convert a string which is expected to be SHOUTY_CASE (but may not be *precisely* shouty) +// into a PascalCase string. Precise rules implemented: + +// Previous input character Current character Case +// Any Non-alphanumeric Skipped +// None - first char of input Alphanumeric Upper +// Non-letter (e.g. _ or 1) Alphanumeric Upper +// Numeric Alphanumeric Upper +// Lower letter Alphanumeric Same as current +// Upper letter Alphanumeric Lower +std::string ShoutyToPascalCase(const std::string& input) { + string result; + // Simple way of implementing "always start with upper" + char previous = '_'; + for (int i = 0; i < input.size(); i++) { + char current = input[i]; + if (!ascii_isalnum(current)) { + previous = current; + continue; + } + if (!ascii_isalnum(previous)) { + result += ascii_toupper(current); + } else if (ascii_isdigit(previous)) { + result += ascii_toupper(current); + } else if (ascii_islower(previous)) { + result += current; + } else { + result += ascii_tolower(current); + } + previous = current; + } + return result; +} + +// Attempt to remove a prefix from a value, ignoring casing and skipping underscores. +// (foo, foo_bar) => bar - underscore after prefix is skipped +// (FOO, foo_bar) => bar - casing is ignored +// (foo_bar, foobarbaz) => baz - underscore in prefix is ignored +// (foobar, foo_barbaz) => baz - underscore in value is ignored +// (foo, bar) => bar - prefix isn't matched; return original value +std::string TryRemovePrefix(const std::string& prefix, const std::string& value) { + // First normalize to a lower-case no-underscores prefix to match against + std::string prefix_to_match = ""; + for (size_t i = 0; i < prefix.size(); i++) { + if (prefix[i] != '_') { + prefix_to_match += ascii_tolower(prefix[i]); + } + } + + // This keeps track of how much of value we've consumed + size_t prefix_index, value_index; + for (prefix_index = 0, value_index = 0; + prefix_index < prefix_to_match.size() && value_index < value.size(); + value_index++) { + // Skip over underscores in the value + if (value[value_index] == '_') { + continue; + } + if (ascii_tolower(value[value_index]) != prefix_to_match[prefix_index++]) { + // Failed to match the prefix - bail out early. + return value; + } + } + + // If we didn't finish looking through the prefix, we can't strip it. + if (prefix_index < prefix_to_match.size()) { + return value; + } + + // Step over any underscores after the prefix + while (value_index < value.size() && value[value_index] == '_') { + value_index++; + } + + // If there's nothing left (e.g. it was a prefix with only underscores afterwards), don't strip. + if (value_index == value.size()) { + return value; + } + + return value.substr(value_index); +} + +// Format the enum value name in a pleasant way for C#: +// - Strip the enum name as a prefix if possible +// - Convert to PascalCase. +// For example, an enum called Color with a value of COLOR_BLUE should +// result in an enum value in C# called just Blue +std::string GetEnumValueName(const std::string& enum_name, const std::string& enum_value_name) { + std::string stripped = TryRemovePrefix(enum_name, enum_value_name); + std::string result = ShoutyToPascalCase(stripped); + // Just in case we have an enum name of FOO and a value of FOO_2... make sure the returned + // string is a valid identifier. + if (ascii_isdigit(result[0])) { + result = "_" + result; + } + return result; +} + std::string ToCSharpName(const std::string& name, const FileDescriptor* file) { std::string result = GetFileNamespace(file); if (result != "") { @@ -218,16 +293,12 @@ std::string ToCSharpName(const std::string& name, const FileDescriptor* file) { return "global::" + result; } -std::string GetUmbrellaClassName(const FileDescriptor* descriptor) { +std::string GetReflectionClassName(const FileDescriptor* descriptor) { std::string result = GetFileNamespace(descriptor); if (!result.empty()) { result += '.'; } - std::string umbrellaNamespace = GetUmbrellaClassNestedNamespace(descriptor); - if (!umbrellaNamespace.empty()) { - result += umbrellaNamespace + "."; - } - result += GetUmbrellaClassUnqualifiedName(descriptor); + result += GetReflectionClassUnqualifiedName(descriptor); return "global::" + result; } @@ -269,6 +340,41 @@ std::string GetPropertyName(const FieldDescriptor* descriptor) { return property_name; } +std::string GetOutputFile( + const google::protobuf::FileDescriptor* descriptor, + const std::string file_extension, + const bool generate_directories, + const std::string base_namespace, + string* error) { + string relative_filename = GetFileNameBase(descriptor) + file_extension; + if (!generate_directories) { + return relative_filename; + } + string ns = GetFileNamespace(descriptor); + string namespace_suffix = ns; + if (!base_namespace.empty()) { + // Check that the base_namespace is either equal to or a leading part of + // the file namespace. This isn't just a simple prefix; "Foo.B" shouldn't + // be regarded as a prefix of "Foo.Bar". The simplest option is to add "." + // to both. + string extended_ns = ns + "."; + if (extended_ns.find(base_namespace + ".") != 0) { + *error = "Namespace " + ns + " is not a prefix namespace of base namespace " + base_namespace; + return ""; // This will be ignored, because we've set an error. + } + namespace_suffix = ns.substr(base_namespace.length()); + if (namespace_suffix.find(".") == 0) { + namespace_suffix = namespace_suffix.substr(1); + } + } + + string namespace_dir = StringReplace(namespace_suffix, ".", "/", true); + if (!namespace_dir.empty()) { + namespace_dir += "/"; + } + return namespace_dir + relative_filename; +} + // TODO: c&p from Java protoc plugin // For encodings with fixed sizes, returns that size in bytes. Otherwise // returns -1. @@ -345,49 +451,50 @@ std::string FileDescriptorToBase64(const FileDescriptor* descriptor) { } FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor, - int fieldOrdinal) { + int fieldOrdinal, + const Options* options) { switch (descriptor->type()) { case FieldDescriptor::TYPE_GROUP: case FieldDescriptor::TYPE_MESSAGE: if (descriptor->is_repeated()) { if (descriptor->is_map()) { - return new MapFieldGenerator(descriptor, fieldOrdinal); + return new MapFieldGenerator(descriptor, fieldOrdinal, options); } else { - return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal); + return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal, options); } } else { if (IsWrapperType(descriptor)) { if (descriptor->containing_oneof()) { - return new WrapperOneofFieldGenerator(descriptor, fieldOrdinal); + return new WrapperOneofFieldGenerator(descriptor, fieldOrdinal, options); } else { - return new WrapperFieldGenerator(descriptor, fieldOrdinal); + return new WrapperFieldGenerator(descriptor, fieldOrdinal, options); } } else { if (descriptor->containing_oneof()) { - return new MessageOneofFieldGenerator(descriptor, fieldOrdinal); + return new MessageOneofFieldGenerator(descriptor, fieldOrdinal, options); } else { - return new MessageFieldGenerator(descriptor, fieldOrdinal); + return new MessageFieldGenerator(descriptor, fieldOrdinal, options); } } } case FieldDescriptor::TYPE_ENUM: if (descriptor->is_repeated()) { - return new RepeatedEnumFieldGenerator(descriptor, fieldOrdinal); + return new RepeatedEnumFieldGenerator(descriptor, fieldOrdinal, options); } else { if (descriptor->containing_oneof()) { - return new EnumOneofFieldGenerator(descriptor, fieldOrdinal); + return new EnumOneofFieldGenerator(descriptor, fieldOrdinal, options); } else { - return new EnumFieldGenerator(descriptor, fieldOrdinal); + return new EnumFieldGenerator(descriptor, fieldOrdinal, options); } } default: if (descriptor->is_repeated()) { - return new RepeatedPrimitiveFieldGenerator(descriptor, fieldOrdinal); + return new RepeatedPrimitiveFieldGenerator(descriptor, fieldOrdinal, options); } else { if (descriptor->containing_oneof()) { - return new PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal); + return new PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal, options); } else { - return new PrimitiveFieldGenerator(descriptor, fieldOrdinal); + return new PrimitiveFieldGenerator(descriptor, fieldOrdinal, options); } } } |