Commit 5835efa3 authored by Wink Saville's avatar Wink Saville Committed by Android Git Automerger

am e7b778b9: Merge "Fix outer classname for javamicro/javanano."

* commit 'e7b778b99f607460ed9ea88a13ec91164cda8537':
  Fix outer classname for javamicro/javanano.
parents bc1543fb 062d561e
This diff is collapsed.
......@@ -33,16 +33,22 @@ package com.google.protobuf;
import com.google.protobuf.nano.CodedInputByteBufferNano;
import com.google.protobuf.nano.Extensions;
import com.google.protobuf.nano.Extensions.AnotherMessage;
import com.google.protobuf.nano.FileScopeEnumRefNano;
import com.google.protobuf.nano.InternalNano;
import com.google.protobuf.nano.MessageNano;
import com.google.protobuf.nano.MessageScopeEnumRefNano;
import com.google.protobuf.nano.MultipleImportingNonMultipleNano1;
import com.google.protobuf.nano.MultipleImportingNonMultipleNano2;
import com.google.protobuf.nano.MultipleNameClashNano;
import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas;
import com.google.protobuf.nano.NanoOuterClass;
import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano;
import com.google.protobuf.nano.RecursiveMessageNano;
import com.google.protobuf.nano.SimpleMessageNano;
import com.google.protobuf.nano.UnittestImportNano;
import com.google.protobuf.nano.UnittestMultipleNano;
import com.google.protobuf.nano.UnittestRecursiveNano.RecursiveMessageNano;
import com.google.protobuf.nano.UnittestSimpleNano.SimpleMessageNano;
import com.google.protobuf.nano.UnittestSingleNano.SingleMessageNano;
import com.google.protobuf.nano.UnittestStringutf8Nano.StringUtf8;
import junit.framework.TestCase;
......@@ -2061,6 +2067,41 @@ public class NanoTest extends TestCase {
assertEquals(nestedMsg2.bb, newMsg.repeatedNestedMessage[2].bb);
}
/**
* Tests that code generation correctly wraps a single message into its outer
* class. The class {@code SingleMessageNano} is imported from the outer
* class {@code UnittestSingleNano}, whose name is implicit. Any error would
* cause this method to fail compilation.
*/
public void testNanoSingle() throws Exception {
SingleMessageNano msg = new SingleMessageNano();
}
/**
* Tests that code generation correctly skips generating the outer class if
* unnecessary, letting a file-scope entity have the same name. The class
* {@code MultipleNameClashNano} shares the same name with the file's outer
* class defined explicitly, but the file contains no other entities and has
* java_multiple_files set. Any error would cause this method to fail
* compilation.
*/
public void testNanoMultipleNameClash() throws Exception {
MultipleNameClashNano msg = new MultipleNameClashNano();
msg.field = 0;
}
/**
* Tests that code generation correctly handles enums in different scopes in
* a source file with the option java_multiple_files set to true. Any error
* would cause this method to fail compilation.
*/
public void testNanoMultipleEnumScoping() throws Exception {
FileScopeEnumRefNano msg1 = new FileScopeEnumRefNano();
msg1.enumField = UnittestMultipleNano.ONE;
MessageScopeEnumRefNano msg2 = new MessageScopeEnumRefNano();
msg2.enumField = MessageScopeEnumRefNano.TWO;
}
/**
* Tests that code generation with mixed values of the java_multiple_files
* options between the main source file and the imported source files would
......
......@@ -69,18 +69,6 @@ EnumGenerator::~EnumGenerator() {}
void EnumGenerator::Generate(io::Printer* printer) {
printer->Print("// enum $classname$\n", "classname", descriptor_->name());
const string& file_name = descriptor_->file()->name();
bool is_own_file = params_.java_multiple_files(file_name) ||
((descriptor_->containing_type() == NULL) &&
!params_.has_java_outer_classname(file_name));
if (is_own_file) {
printer->Print("public final class $classname$ {\n", "classname",
descriptor_->name());
printer->Indent();
printer->Print("private $classname$() {}\n", "classname",
descriptor_->name());
}
for (int i = 0; i < canonical_values_.size(); i++) {
map<string, string> vars;
vars["name"] = RenameJavaKeywords(canonical_values_[i]->name());
......@@ -99,10 +87,6 @@ void EnumGenerator::Generate(io::Printer* printer) {
"public static final int $name$ = $canonical_name$;\n");
}
if (is_own_file) {
printer->Outdent();
printer->Print("}");
}
printer->Print("\n");
}
......
......@@ -32,6 +32,8 @@
// Based on original Protocol Buffers design by
// Sanjay Ghemawat, Jeff Dean, and others.
#include <iostream>
#include <google/protobuf/compiler/javanano/javanano_file.h>
#include <google/protobuf/compiler/javanano/javanano_enum.h>
#include <google/protobuf/compiler/javanano/javanano_extension.h>
......@@ -103,56 +105,48 @@ bool FileGenerator::Validate(string* error) {
return false;
}
// If there is no outer class name then there must be only
// message and no enums defined in the file scope.
if (!params_.has_java_outer_classname(file_->name())) {
if (file_->message_type_count() != 1) {
error->assign(file_->name());
error->append(
": Java NANO_RUNTIME may only have 1 message if there is no 'option java_outer_classname'\"");
return false;
}
if (file_->service_count() != 0) {
error->assign(file_->name());
error->append(
": Java NANO_RUNTIME does not support services\"");
return false;
}
if (file_->enum_type_count() != 0) {
error->assign(file_->name());
error->append(
": Java NANO_RUNTIME must have an 'option java_outer_classname' if file scope enums are present\"");
return false;
}
if (!IsOuterClassNeeded(params_, file_)) {
return true;
}
// Check whether legacy javanano generator would omit the outer class.
if (!params_.has_java_outer_classname(file_->name())
&& file_->message_type_count() == 1
&& file_->enum_type_count() == 0 && file_->extension_count() == 0) {
cout << "INFO: " << file_->name() << ":" << endl;
cout << "Javanano generator has changed to align with java generator. "
"An outer class will be created for this file and the single message "
"in the file will become a nested class. Use java_multiple_files to "
"skip generating the outer class, or set an explicit "
"java_outer_classname to suppress this message." << endl;
}
// Check that no class name matches the file's class name. This is a common
// problem that leads to Java compile errors that can be hard to understand.
// It's especially bad when using the java_multiple_files, since we would
// end up overwriting the outer class with one of the inner ones.
int found_fileName = 0;
for (int i = 0; i < file_->enum_type_count(); i++) {
if (file_->enum_type(i)->name() == classname_) {
found_fileName += 1;
}
}
for (int i = 0; i < file_->message_type_count(); i++) {
bool found_conflict = false;
for (int i = 0; !found_conflict && i < file_->message_type_count(); i++) {
if (file_->message_type(i)->name() == classname_) {
found_fileName += 1;
found_conflict = true;
}
}
if (file_->service_count() != 0) {
if (found_conflict) {
error->assign(file_->name());
error->append(
": Java NANO_RUNTIME does not support services\"");
return false;
}
if (found_fileName > 1) {
error->assign(file_->name());
error->append(
": Cannot generate Java output because there is more than one class name, \"");
": Cannot generate Java output because the file's outer class name, \"");
error->append(classname_);
error->append(
"\", matches the name of one of the types declared inside it. "
"Please either rename the type or use the java_outer_classname "
"option to specify a different outer class name for the .proto file."
" -- FIX THIS MESSAGE");
"option to specify a different outer class name for the .proto file.");
return false;
}
return true;
......@@ -171,13 +165,11 @@ void FileGenerator::Generate(io::Printer* printer) {
"package", java_package_);
}
if (params_.has_java_outer_classname(file_->name())) {
printer->Print(
"public final class $classname$ {\n"
" private $classname$() {}\n",
"classname", classname_);
printer->Indent();
}
printer->Print(
"public final class $classname$ {\n"
" private $classname$() {}\n",
"classname", classname_);
printer->Indent();
// -----------------------------------------------------------------
......@@ -186,10 +178,13 @@ void FileGenerator::Generate(io::Printer* printer) {
ExtensionGenerator(file_->extension(i), params_).Generate(printer);
}
// Enums.
for (int i = 0; i < file_->enum_type_count(); i++) {
EnumGenerator(file_->enum_type(i), params_).Generate(printer);
}
// Messages.
if (!params_.java_multiple_files(file_->name())) {
for (int i = 0; i < file_->enum_type_count(); i++) {
EnumGenerator(file_->enum_type(i), params_).Generate(printer);
}
for (int i = 0; i < file_->message_type_count(); i++) {
MessageGenerator(file_->message_type(i), params_).Generate(printer);
}
......@@ -201,11 +196,9 @@ void FileGenerator::Generate(io::Printer* printer) {
MessageGenerator(file_->message_type(i), params_).GenerateStaticVariables(printer);
}
if (params_.has_java_outer_classname(file_->name())) {
printer->Outdent();
printer->Print(
"}\n");
}
printer->Outdent();
printer->Print(
"}\n");
}
template<typename GeneratorClass, typename DescriptorClass>
......@@ -239,11 +232,6 @@ void FileGenerator::GenerateSiblings(const string& package_dir,
OutputDirectory* output_directory,
vector<string>* file_list) {
if (params_.java_multiple_files(file_->name())) {
for (int i = 0; i < file_->enum_type_count(); i++) {
GenerateSibling<EnumGenerator>(package_dir, java_package_,
file_->enum_type(i),
output_directory, file_list, params_);
}
for (int i = 0; i < file_->message_type_count(); i++) {
GenerateSibling<MessageGenerator>(package_dir, java_package_,
file_->message_type(i),
......
......@@ -138,16 +138,18 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file,
vector<string> all_files;
string java_filename = package_dir;
java_filename += file_generator.classname();
java_filename += ".java";
all_files.push_back(java_filename);
// Generate main java file.
scoped_ptr<io::ZeroCopyOutputStream> output(
output_directory->Open(java_filename));
io::Printer printer(output.get(), '$');
file_generator.Generate(&printer);
if (IsOuterClassNeeded(params, file)) {
string java_filename = package_dir;
java_filename += file_generator.classname();
java_filename += ".java";
all_files.push_back(java_filename);
// Generate main java file.
scoped_ptr<io::ZeroCopyOutputStream> output(
output_directory->Open(java_filename));
io::Printer printer(output.get(), '$');
file_generator.Generate(&printer);
}
// Generate sibling files.
file_generator.GenerateSiblings(package_dir, output_directory, &all_files);
......
......@@ -167,31 +167,20 @@ string StripProto(const string& filename) {
}
string FileClassName(const Params& params, const FileDescriptor* file) {
string name;
if (params.has_java_outer_classname(file->name())) {
name = params.java_outer_classname(file->name());
return params.java_outer_classname(file->name());
} else {
if ((file->message_type_count() == 1)
|| (file->enum_type_count() == 0)) {
// If no outer calls and only one message then
// use the message name as the file name
name = file->message_type(0)->name();
// Use the filename itself with underscores removed
// and a CamelCase style name.
string basename;
string::size_type last_slash = file->name().find_last_of('/');
if (last_slash == string::npos) {
basename = file->name();
} else {
// Use the filename it self with underscores removed
// and a CamelCase style name.
string basename;
string::size_type last_slash = file->name().find_last_of('/');
if (last_slash == string::npos) {
basename = file->name();
} else {
basename = file->name().substr(last_slash + 1);
}
name = UnderscoresToCamelCaseImpl(StripProto(basename), true);
basename = file->name().substr(last_slash + 1);
}
return UnderscoresToCamelCaseImpl(StripProto(basename), true);
}
return name;
}
string FileJavaPackage(const Params& params, const FileDescriptor* file) {
......@@ -207,38 +196,27 @@ string FileJavaPackage(const Params& params, const FileDescriptor* file) {
}
}
string ToJavaName(const Params& params, const string& full_name,
const FileDescriptor* file) {
string result;
if (params.java_multiple_files(file->name())) {
result = FileJavaPackage(params, file);
} else {
result = ClassName(params, file);
bool IsOuterClassNeeded(const Params& params, const FileDescriptor* file) {
// Enums and extensions need the outer class as the scope.
if (file->enum_type_count() != 0 || file->extension_count() != 0) {
return true;
}
if (file->package().empty()) {
result += '.';
result += full_name;
// Messages need the outer class only if java_multiple_files is false.
return !params.java_multiple_files(file->name());
}
string ToJavaName(const Params& params, const string& name, bool is_class,
const Descriptor* parent, const FileDescriptor* file) {
string result;
if (parent != NULL) {
result.append(ClassName(params, parent));
} else if (is_class && params.java_multiple_files(file->name())) {
result.append(FileJavaPackage(params, file));
} else {
// Strip the proto package from full_name since we've replaced it with
// the Java package. If there isn't an outer classname then strip it too.
int sizeToSkipPackageName = file->package().size();
int sizeToSkipOutClassName;
if (params.has_java_outer_classname(file->name())) {
sizeToSkipOutClassName = 0;
} else {
sizeToSkipOutClassName =
full_name.find_first_of('.', sizeToSkipPackageName + 1);
}
int sizeToSkip = sizeToSkipOutClassName > 0 ?
sizeToSkipOutClassName : sizeToSkipPackageName;
string class_name = full_name.substr(sizeToSkip + 1);
if (class_name == FileClassName(params, file)) {
// Done class_name is already present.
} else {
result += '.';
result += class_name;
}
result.append(ClassName(params, file));
}
if (!result.empty()) result.append(1, '.');
result.append(RenameJavaKeywords(name));
return result;
}
......@@ -250,61 +228,14 @@ string ClassName(const Params& params, const FileDescriptor* descriptor) {
}
string ClassName(const Params& params, const EnumDescriptor* descriptor) {
string result;
const FileDescriptor* file = descriptor->file();
const string file_name = file->name();
const string full_name = descriptor->full_name();
// Remove enum class name as we use int's for enums
int last_dot_in_name = full_name.find_last_of('.');
string base_name = full_name.substr(0, last_dot_in_name);
if (!file->package().empty()) {
if (file->package() == base_name.substr(0, file->package().size())) {
// Remove package name leaving just the parent class of the enum
int offset = file->package().size();
if (base_name.size() > offset) {
// Remove period between package and class name if there is a classname
offset += 1;
}
base_name = base_name.substr(offset);
} else {
GOOGLE_LOG(FATAL) << "Expected package name to start an enum";
}
}
// Construct the path name from the package and outer class
// Add the java package name if it exists
if (params.has_java_package(file_name)) {
result += params.java_package(file_name);
}
// If the java_multiple_files option is present, we will generate enums into separate
// classes, each named after the original enum type. This takes precedence over
// any outer_classname.
if (params.java_multiple_files(file_name) && last_dot_in_name != string::npos) {
string enum_simple_name = full_name.substr(last_dot_in_name + 1);
if (!result.empty()) {
result += ".";
}
result += enum_simple_name;
} else if (params.has_java_outer_classname(file_name)) {
// Add the outer classname if it exists
if (!result.empty()) {
result += ".";
}
result += params.java_outer_classname(file_name);
}
// Create the full class name from the base and path
if (!base_name.empty()) {
if (!result.empty()) {
result += ".";
}
result += base_name;
// An enum's class name is the enclosing message's class name or the outer
// class name.
const Descriptor* parent = descriptor->containing_type();
if (parent != NULL) {
return ClassName(params, parent);
} else {
return ClassName(params, descriptor->file());
}
return result;
}
string FieldConstantName(const FieldDescriptor *field) {
......
......@@ -73,25 +73,35 @@ string FileClassName(const Params& params, const FileDescriptor* file);
// Returns the file's Java package name.
string FileJavaPackage(const Params& params, const FileDescriptor* file);
// Converts the given fully-qualified name in the proto namespace to its
// fully-qualified name in the Java namespace, given that it is in the given
// file.
string ToJavaName(const Params& params, const string& full_name,
const FileDescriptor* file);
// Returns whether the Java outer class is needed, i.e. whether the option
// java_multiple_files is false, or the proto file contains any file-scope
// enums/extensions.
bool IsOuterClassNeeded(const Params& params, const FileDescriptor* file);
// Converts the given simple name of a proto entity to its fully-qualified name
// in the Java namespace, given that it is in the given file enclosed in the
// given parent message (or NULL for file-scope entities). Whether the file's
// outer class name should be included in the return value depends on factors
// inferrable from the given arguments, including is_class which indicates
// whether the entity translates to a Java class.
string ToJavaName(const Params& params, const string& name, bool is_class,
const Descriptor* parent, const FileDescriptor* file);
// These return the fully-qualified class name corresponding to the given
// descriptor.
inline string ClassName(const Params& params, const Descriptor* descriptor) {
return ToJavaName(params, descriptor->full_name(), descriptor->file());
return ToJavaName(params, descriptor->name(), true,
descriptor->containing_type(), descriptor->file());
}
string ClassName(const Params& params, const EnumDescriptor* descriptor);
inline string ClassName(const Params& params,
const ServiceDescriptor* descriptor) {
return ToJavaName(params, descriptor->full_name(), descriptor->file());
return ToJavaName(params, descriptor->name(), true, NULL, descriptor->file());
}
inline string ExtensionIdentifierName(const Params& params,
const FieldDescriptor* descriptor) {
return ToJavaName(params, descriptor->full_name(), descriptor->file());
return ToJavaName(params, descriptor->name(), false,
descriptor->extension_scope(), descriptor->file());
}
string ClassName(const Params& params, const FileDescriptor* descriptor);
......
......@@ -114,7 +114,7 @@ void MessageGenerator::GenerateStaticVariableInitializers(
io::Printer* printer) {
// Generate static member initializers for all nested types.
for (int i = 0; i < descriptor_->nested_type_count(); i++) {
// TODO(kenton): Reuse MessageGenerator objects?
// TODO(kenton): Reuse MessageGenerator objects?
MessageGenerator(descriptor_->nested_type(i), params_)
.GenerateStaticVariableInitializers(printer);
}
......@@ -124,15 +124,7 @@ void MessageGenerator::Generate(io::Printer* printer) {
const string& file_name = descriptor_->file()->name();
bool is_own_file =
params_.java_multiple_files(file_name)
|| ((descriptor_->containing_type() == NULL)
&& !params_.has_java_outer_classname(file_name));
#if 0
GOOGLE_LOG(INFO) << "is_own_file=" << is_own_file;
GOOGLE_LOG(INFO) << "containing_type()=" << ((descriptor_->containing_type() == NULL) ? "NULL" : "not null");
GOOGLE_LOG(INFO) << "java_multiple_files()=" << params_.java_multiple_files();
GOOGLE_LOG(INFO) << "has_java_outer_classname()=" << params_.has_java_outer_classname(file_->name());
#endif
&& descriptor_->containing_type() == NULL;
if (!params_.store_unknown_fields() &&
(descriptor_->extension_count() != 0 || descriptor_->extension_range_count() != 0)) {
......@@ -355,25 +347,21 @@ void MessageGenerator::GenerateMergeFromMethods(io::Printer* printer) {
void MessageGenerator::
GenerateParseFromMethods(io::Printer* printer) {
bool is_own_file =
descriptor_->containing_type() == NULL;
// Note: These are separate from GenerateMessageSerializationMethods()
// because they need to be generated even for messages that are optimized
// for code size.
printer->Print(
"public $static$ $classname$ parseFrom(byte[] data)\n"
"public static $classname$ parseFrom(byte[] data)\n"
" throws com.google.protobuf.nano.InvalidProtocolBufferNanoException {\n"
" return com.google.protobuf.nano.MessageNano.mergeFrom(new $classname$(), data);\n"
"}\n"
"\n"
"public $static$ $classname$ parseFrom(\n"
"public static $classname$ parseFrom(\n"
" com.google.protobuf.nano.CodedInputByteBufferNano input)\n"
" throws java.io.IOException {\n"
" return new $classname$().mergeFrom(input);\n"
"}\n"
"\n",
"static", (is_own_file ? "static" : ""),
"classname", descriptor_->name());
}
......
......@@ -28,26 +28,14 @@
// (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: bduff@google.com (Brian Duff)
//
// Author: maxtroy@google.com (Max Cai)
package protobuf_unittest_import;
option java_package = "com.google.protobuf.nano";
option java_outer_classname = "NanoEnumsWithMultipleFiles";
option java_outer_classname = "MultipleNameClashNano";
option java_multiple_files = true;
enum FirstTopLevelEnum {
FIRST_TOP_LEVEL_FIRST = 1;
FIRST_TOP_LEVEL_SECOND = 2;
}
enum SecondTopLevelEnum {
SECOND_TOP_LEVEL_FIRST = 1;
SECOND_TOP_LEVEL_SECOND = 2;
}
message SomeMessage {
optional FirstTopLevelEnum first = 1;
optional SecondTopLevelEnum second = 2;
message MultipleNameClashNano {
optional int32 field = 1;
}
......@@ -35,9 +35,25 @@ package protobuf_unittest_import;
import "google/protobuf/unittest_import_nano.proto";
option java_package = "com.google.protobuf.nano";
option java_outer_classname = "NanoMultipleImportingNonMultiple";
option java_multiple_files = true;
enum FileScopeEnum {
ONE = 1;
TWO = 2;
}
message FileScopeEnumRefNano {
optional FileScopeEnum enum_field = 1;
}
message MessageScopeEnumRefNano {
enum MessageScopeEnum {
ONE = 1;
TWO = 2;
}
optional MessageScopeEnum enum_field = 1;
}
message MultipleImportingNonMultipleNano1 {
optional ImportMessageNano field = 1;
}
......
......@@ -34,6 +34,8 @@
package protobuf_unittest_import;
option java_package = "com.google.protobuf.nano";
// Explicit outer classname to suppress legacy info.
option java_outer_classname = "UnittestRecursiveNano";
message RecursiveMessageNano {
message NestedMessage {
......
......@@ -34,6 +34,8 @@
package protobuf_unittest_import;
option java_package = "com.google.protobuf.nano";
// Explicit outer classname to suppress legacy info.
option java_outer_classname = "UnittestSimpleNano";
message SimpleMessageNano {
message NestedMessage {
......
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// http://code.google.com/p/protobuf/
//
// 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: maxtroy@google.com (Max Cai)
package protobuf_unittest_import;
option java_package = "com.google.protobuf.nano";
message SingleMessageNano {
}
......@@ -34,6 +34,8 @@
package protobuf_unittest_import;
option java_package = "com.google.protobuf.nano";
// Explicit outer classname to suppress legacy info.
option java_outer_classname = "UnittestStringutf8Nano";
message StringUtf8 {
optional string id = 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment