javanano_helpers.cc revision f4e01452f159ae6b53f5edd25fa647ca2919ae10
1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// http://code.google.com/p/protobuf/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// Author: kenton@google.com (Kenton Varda)
32//  Based on original Protocol Buffers design by
33//  Sanjay Ghemawat, Jeff Dean, and others.
34
35#include <vector>
36
37#include <google/protobuf/compiler/javanano/javanano_helpers.h>
38#include <google/protobuf/compiler/javanano/javanano_params.h>
39#include <google/protobuf/descriptor.pb.h>
40#include <google/protobuf/stubs/hash.h>
41#include <google/protobuf/stubs/strutil.h>
42#include <google/protobuf/stubs/substitute.h>
43
44namespace google {
45namespace protobuf {
46namespace compiler {
47namespace javanano {
48
49const char kThickSeparator[] =
50  "// ===================================================================\n";
51const char kThinSeparator[] =
52  "// -------------------------------------------------------------------\n";
53
54class RenameKeywords {
55 private:
56  hash_set<string> java_keywords_set_;
57
58 public:
59  RenameKeywords() {
60    static const char* kJavaKeywordsList[] = {
61      // Reserved Java Keywords
62      "abstract", "assert", "boolean", "break", "byte", "case", "catch",
63      "char", "class", "const", "continue", "default", "do", "double", "else",
64      "enum", "extends", "final", "finally", "float", "for", "goto", "if",
65      "implements", "import", "instanceof", "int", "interface", "long",
66      "native", "new", "package", "private", "protected", "public", "return",
67      "short", "static", "strictfp", "super", "switch", "synchronized",
68      "this", "throw", "throws", "transient", "try", "void", "volatile", "while",
69
70      // Reserved Keywords for Literals
71      "false", "null", "true"
72    };
73
74    for (int i = 0; i < GOOGLE_ARRAYSIZE(kJavaKeywordsList); i++) {
75      java_keywords_set_.insert(kJavaKeywordsList[i]);
76    }
77  }
78
79  // Used to rename the a field name if it's a java keyword.  Specifically
80  // this is used to rename the ["name"] or ["capitalized_name"] field params.
81  // (http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html)
82  string RenameJavaKeywordsImpl(const string& input) {
83    string result = input;
84
85    if (java_keywords_set_.find(result) != java_keywords_set_.end()) {
86      result += "_";
87    }
88
89    return result;
90  }
91
92};
93
94static RenameKeywords sRenameKeywords;
95
96namespace {
97
98const char* kDefaultPackage = "";
99
100const string& FieldName(const FieldDescriptor* field) {
101  // Groups are hacky:  The name of the field is just the lower-cased name
102  // of the group type.  In Java, though, we would like to retain the original
103  // capitalization of the type name.
104  if (field->type() == FieldDescriptor::TYPE_GROUP) {
105    return field->message_type()->name();
106  } else {
107    return field->name();
108  }
109}
110
111string UnderscoresToCamelCaseImpl(const string& input, bool cap_next_letter) {
112  string result;
113  // Note:  I distrust ctype.h due to locales.
114  for (int i = 0; i < input.size(); i++) {
115    if ('a' <= input[i] && input[i] <= 'z') {
116      if (cap_next_letter) {
117        result += input[i] + ('A' - 'a');
118      } else {
119        result += input[i];
120      }
121      cap_next_letter = false;
122    } else if ('A' <= input[i] && input[i] <= 'Z') {
123      if (i == 0 && !cap_next_letter) {
124        // Force first letter to lower-case unless explicitly told to
125        // capitalize it.
126        result += input[i] + ('a' - 'A');
127      } else {
128        // Capital letters after the first are left as-is.
129        result += input[i];
130      }
131      cap_next_letter = false;
132    } else if ('0' <= input[i] && input[i] <= '9') {
133      result += input[i];
134      cap_next_letter = true;
135    } else {
136      cap_next_letter = true;
137    }
138  }
139  return result;
140}
141
142}  // namespace
143
144string UnderscoresToCamelCase(const FieldDescriptor* field) {
145  return UnderscoresToCamelCaseImpl(FieldName(field), false);
146}
147
148string UnderscoresToCapitalizedCamelCase(const FieldDescriptor* field) {
149  return UnderscoresToCamelCaseImpl(FieldName(field), true);
150}
151
152string UnderscoresToCamelCase(const MethodDescriptor* method) {
153  return UnderscoresToCamelCaseImpl(method->name(), false);
154}
155
156string RenameJavaKeywords(const string& input) {
157  return sRenameKeywords.RenameJavaKeywordsImpl(input);
158}
159
160string StripProto(const string& filename) {
161  if (HasSuffixString(filename, ".protodevel")) {
162    return StripSuffixString(filename, ".protodevel");
163  } else {
164    return StripSuffixString(filename, ".proto");
165  }
166}
167
168string FileClassName(const Params& params, const FileDescriptor* file) {
169  string name;
170
171  if (params.has_java_outer_classname(file->name())) {
172      name = params.java_outer_classname(file->name());
173  } else {
174    if ((file->message_type_count() == 1)
175        || (file->enum_type_count() == 0)) {
176      // If no outer calls and only one message then
177      // use the message name as the file name
178      name = file->message_type(0)->name();
179    } else {
180      // Use the filename it self with underscores removed
181      // and a CamelCase style name.
182      string basename;
183      string::size_type last_slash = file->name().find_last_of('/');
184      if (last_slash == string::npos) {
185        basename = file->name();
186      } else {
187        basename = file->name().substr(last_slash + 1);
188      }
189      name = UnderscoresToCamelCaseImpl(StripProto(basename), true);
190    }
191  }
192
193  return name;
194}
195
196string FileJavaPackage(const Params& params, const FileDescriptor* file) {
197  if (params.has_java_package(file->name())) {
198    return params.java_package(file->name());
199  } else {
200    string result = kDefaultPackage;
201    if (!file->package().empty()) {
202      if (!result.empty()) result += '.';
203      result += file->package();
204    }
205    return result;
206  }
207}
208
209string ToJavaName(const Params& params, const string& full_name,
210    const FileDescriptor* file) {
211  string result;
212  if (params.java_multiple_files()) {
213    result = FileJavaPackage(params, file);
214  } else {
215    result = ClassName(params, file);
216  }
217  if (file->package().empty()) {
218    result += '.';
219    result += full_name;
220  } else {
221    // Strip the proto package from full_name since we've replaced it with
222    // the Java package. If there isn't an outer classname then strip it too.
223    int sizeToSkipPackageName = file->package().size();
224    int sizeToSkipOutClassName;
225    if (params.has_java_outer_classname(file->name())) {
226      sizeToSkipOutClassName = 0;
227    } else {
228      sizeToSkipOutClassName =
229                full_name.find_first_of('.', sizeToSkipPackageName + 1);
230    }
231    int sizeToSkip = sizeToSkipOutClassName > 0 ?
232            sizeToSkipOutClassName : sizeToSkipPackageName;
233    string class_name = full_name.substr(sizeToSkip + 1);
234    if (class_name == FileClassName(params, file)) {
235      // Done class_name is already present.
236    } else {
237      result += '.';
238      result += class_name;
239    }
240  }
241  return result;
242}
243
244string ClassName(const Params& params, const FileDescriptor* descriptor) {
245  string result = FileJavaPackage(params, descriptor);
246  if (!result.empty()) result += '.';
247  result += FileClassName(params, descriptor);
248  return result;
249}
250
251string ClassName(const Params& params, const EnumDescriptor* descriptor) {
252  string result;
253  const FileDescriptor* file = descriptor->file();
254  const string file_name = file->name();
255  const string full_name = descriptor->full_name();
256
257  // Remove enum class name as we use int's for enums
258  int last_dot_in_name = full_name.find_last_of('.');
259  string base_name = full_name.substr(0, last_dot_in_name);
260
261  if (!file->package().empty()) {
262    if (file->package() == base_name.substr(0, file->package().size())) {
263      // Remove package name leaving just the parent class of the enum
264      int offset = file->package().size();
265      if (base_name.size() > offset) {
266        // Remove period between package and class name if there is a classname
267        offset += 1;
268      }
269      base_name = base_name.substr(offset);
270    } else {
271      GOOGLE_LOG(FATAL) << "Expected package name to start an enum";
272    }
273  }
274
275  // Construct the path name from the package and outer class
276
277  // Add the java package name if it exists
278  if (params.has_java_package(file_name)) {
279    result += params.java_package(file_name);
280  }
281
282  // If the java_multiple_files option is present, we will generate enums into separate
283  // classes, each named after the original enum type. This takes precedence over
284  // any outer_classname.
285  if (params.java_multiple_files() && last_dot_in_name != string::npos) {
286    string enum_simple_name = full_name.substr(last_dot_in_name + 1);
287    if (!result.empty()) {
288      result += ".";
289    }
290    result += enum_simple_name;
291  } else if (params.has_java_outer_classname(file_name)) {
292    // Add the outer classname if it exists
293    if (!result.empty()) {
294      result += ".";
295    }
296    result += params.java_outer_classname(file_name);
297  }
298
299  // Create the full class name from the base and path
300  if (!base_name.empty()) {
301    if (!result.empty()) {
302      result += ".";
303    }
304    result += base_name;
305  }
306  return result;
307}
308
309string FieldConstantName(const FieldDescriptor *field) {
310  string name = field->name() + "_FIELD_NUMBER";
311  UpperString(&name);
312  return name;
313}
314
315string FieldDefaultConstantName(const FieldDescriptor *field) {
316  string name = field->name() + "_DEFAULT";
317  UpperString(&name);
318  return name;
319}
320
321JavaType GetJavaType(FieldDescriptor::Type field_type) {
322  switch (field_type) {
323    case FieldDescriptor::TYPE_INT32:
324    case FieldDescriptor::TYPE_UINT32:
325    case FieldDescriptor::TYPE_SINT32:
326    case FieldDescriptor::TYPE_FIXED32:
327    case FieldDescriptor::TYPE_SFIXED32:
328      return JAVATYPE_INT;
329
330    case FieldDescriptor::TYPE_INT64:
331    case FieldDescriptor::TYPE_UINT64:
332    case FieldDescriptor::TYPE_SINT64:
333    case FieldDescriptor::TYPE_FIXED64:
334    case FieldDescriptor::TYPE_SFIXED64:
335      return JAVATYPE_LONG;
336
337    case FieldDescriptor::TYPE_FLOAT:
338      return JAVATYPE_FLOAT;
339
340    case FieldDescriptor::TYPE_DOUBLE:
341      return JAVATYPE_DOUBLE;
342
343    case FieldDescriptor::TYPE_BOOL:
344      return JAVATYPE_BOOLEAN;
345
346    case FieldDescriptor::TYPE_STRING:
347      return JAVATYPE_STRING;
348
349    case FieldDescriptor::TYPE_BYTES:
350      return JAVATYPE_BYTES;
351
352    case FieldDescriptor::TYPE_ENUM:
353      return JAVATYPE_ENUM;
354
355    case FieldDescriptor::TYPE_GROUP:
356    case FieldDescriptor::TYPE_MESSAGE:
357      return JAVATYPE_MESSAGE;
358
359    // No default because we want the compiler to complain if any new
360    // types are added.
361  }
362
363  GOOGLE_LOG(FATAL) << "Can't get here.";
364  return JAVATYPE_INT;
365}
366
367const char* BoxedPrimitiveTypeName(JavaType type) {
368  switch (type) {
369    case JAVATYPE_INT    : return "java.lang.Integer";
370    case JAVATYPE_LONG   : return "java.lang.Long";
371    case JAVATYPE_FLOAT  : return "java.lang.Float";
372    case JAVATYPE_DOUBLE : return "java.lang.Double";
373    case JAVATYPE_BOOLEAN: return "java.lang.Boolean";
374    case JAVATYPE_STRING : return "java.lang.String";
375    case JAVATYPE_BYTES  : return "byte[]";
376    case JAVATYPE_ENUM   : return "java.lang.Integer";
377    case JAVATYPE_MESSAGE: return NULL;
378
379    // No default because we want the compiler to complain if any new
380    // JavaTypes are added.
381  }
382
383  GOOGLE_LOG(FATAL) << "Can't get here.";
384  return NULL;
385}
386
387string EmptyArrayName(const Params& params, const FieldDescriptor* field) {
388  switch (GetJavaType(field)) {
389    case JAVATYPE_INT    : return "com.google.protobuf.nano.WireFormatNano.EMPTY_INT_ARRAY";
390    case JAVATYPE_LONG   : return "com.google.protobuf.nano.WireFormatNano.EMPTY_LONG_ARRAY";
391    case JAVATYPE_FLOAT  : return "com.google.protobuf.nano.WireFormatNano.EMPTY_FLOAT_ARRAY";
392    case JAVATYPE_DOUBLE : return "com.google.protobuf.nano.WireFormatNano.EMPTY_DOUBLE_ARRAY";
393    case JAVATYPE_BOOLEAN: return "com.google.protobuf.nano.WireFormatNano.EMPTY_BOOLEAN_ARRAY";
394    case JAVATYPE_STRING : return "com.google.protobuf.nano.WireFormatNano.EMPTY_STRING_ARRAY";
395    case JAVATYPE_BYTES  : return "com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES_ARRAY";
396    case JAVATYPE_ENUM   : return "com.google.protobuf.nano.WireFormatNano.EMPTY_INT_ARRAY";
397    case JAVATYPE_MESSAGE: return ClassName(params, field->message_type()) + ".EMPTY_ARRAY";
398
399    // No default because we want the compiler to complain if any new
400    // JavaTypes are added.
401  }
402
403  GOOGLE_LOG(FATAL) << "Can't get here.";
404  return "";
405}
406
407string DefaultValue(const Params& params, const FieldDescriptor* field) {
408  if (field->label() == FieldDescriptor::LABEL_REPEATED) {
409    return EmptyArrayName(params, field);
410  }
411
412  // Switch on cpp_type since we need to know which default_value_* method
413  // of FieldDescriptor to call.
414  switch (field->cpp_type()) {
415    case FieldDescriptor::CPPTYPE_INT32:
416      return SimpleItoa(field->default_value_int32());
417    case FieldDescriptor::CPPTYPE_UINT32:
418      // Need to print as a signed int since Java has no unsigned.
419      return SimpleItoa(static_cast<int32>(field->default_value_uint32()));
420    case FieldDescriptor::CPPTYPE_INT64:
421      return SimpleItoa(field->default_value_int64()) + "L";
422    case FieldDescriptor::CPPTYPE_UINT64:
423      return SimpleItoa(static_cast<int64>(field->default_value_uint64())) +
424             "L";
425    case FieldDescriptor::CPPTYPE_DOUBLE:
426      return SimpleDtoa(field->default_value_double()) + "D";
427    case FieldDescriptor::CPPTYPE_FLOAT:
428      return SimpleFtoa(field->default_value_float()) + "F";
429    case FieldDescriptor::CPPTYPE_BOOL:
430      return field->default_value_bool() ? "true" : "false";
431    case FieldDescriptor::CPPTYPE_STRING:
432      if (!field->default_value_string().empty()) {
433        // Point it to the static final in the generated code.
434        return FieldDefaultConstantName(field);
435      } else {
436        if (field->type() == FieldDescriptor::TYPE_BYTES) {
437          return "com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES";
438        } else {
439          return "\"\"";
440        }
441      }
442
443    case FieldDescriptor::CPPTYPE_ENUM:
444      return ClassName(params, field->enum_type()) + "." +
445             field->default_value_enum()->name();
446
447    case FieldDescriptor::CPPTYPE_MESSAGE:
448      return "null";
449
450    // No default because we want the compiler to complain if any new
451    // types are added.
452  }
453
454  GOOGLE_LOG(FATAL) << "Can't get here.";
455  return "";
456}
457
458}  // namespace javanano
459}  // namespace compiler
460}  // namespace protobuf
461}  // namespace google
462