javamicro_helpers.cc revision 0f2ca89132ab81441f7eb351c7a053a8c8d9d1c3
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 <limits>
36#include <vector>
37
38#include <google/protobuf/compiler/javamicro/javamicro_helpers.h>
39#include <google/protobuf/compiler/javamicro/javamicro_params.h>
40#include <google/protobuf/descriptor.pb.h>
41#include <google/protobuf/stubs/strutil.h>
42#include <google/protobuf/stubs/substitute.h>
43
44namespace google {
45namespace protobuf {
46namespace compiler {
47namespace javamicro {
48
49const char kThickSeparator[] =
50  "// ===================================================================\n";
51const char kThinSeparator[] =
52  "// -------------------------------------------------------------------\n";
53
54namespace {
55
56const char* kDefaultPackage = "";
57
58const string& FieldName(const FieldDescriptor* field) {
59  // Groups are hacky:  The name of the field is just the lower-cased name
60  // of the group type.  In Java, though, we would like to retain the original
61  // capitalization of the type name.
62  if (field->type() == FieldDescriptor::TYPE_GROUP) {
63    return field->message_type()->name();
64  } else {
65    return field->name();
66  }
67}
68
69string UnderscoresToCamelCaseImpl(const string& input, bool cap_next_letter) {
70  string result;
71  // Note:  I distrust ctype.h due to locales.
72  for (int i = 0; i < input.size(); i++) {
73    if ('a' <= input[i] && input[i] <= 'z') {
74      if (cap_next_letter) {
75        result += input[i] + ('A' - 'a');
76      } else {
77        result += input[i];
78      }
79      cap_next_letter = false;
80    } else if ('A' <= input[i] && input[i] <= 'Z') {
81      if (i == 0 && !cap_next_letter) {
82        // Force first letter to lower-case unless explicitly told to
83        // capitalize it.
84        result += input[i] + ('a' - 'A');
85      } else {
86        // Capital letters after the first are left as-is.
87        result += input[i];
88      }
89      cap_next_letter = false;
90    } else if ('0' <= input[i] && input[i] <= '9') {
91      result += input[i];
92      cap_next_letter = true;
93    } else {
94      cap_next_letter = true;
95    }
96  }
97  return result;
98}
99
100}  // namespace
101
102string UnderscoresToCamelCase(const FieldDescriptor* field) {
103  return UnderscoresToCamelCaseImpl(FieldName(field), false);
104}
105
106string UnderscoresToCapitalizedCamelCase(const FieldDescriptor* field) {
107  return UnderscoresToCamelCaseImpl(FieldName(field), true);
108}
109
110string UnderscoresToCamelCase(const MethodDescriptor* method) {
111  return UnderscoresToCamelCaseImpl(method->name(), false);
112}
113
114string StripProto(const string& filename) {
115  if (HasSuffixString(filename, ".protodevel")) {
116    return StripSuffixString(filename, ".protodevel");
117  } else {
118    return StripSuffixString(filename, ".proto");
119  }
120}
121
122string FileClassName(const Params& params, const FileDescriptor* file) {
123  string name;
124
125  if (params.has_java_outer_classname(file->name())) {
126      name = params.java_outer_classname(file->name());
127  } else {
128    if ((file->message_type_count() == 1)
129        || (file->enum_type_count() == 0)) {
130      // If no outer calls and only one message then
131      // use the message name as the file name
132      name = file->message_type(0)->name();
133    } else {
134      // Use the filename it self with underscores removed
135      // and a CamelCase style name.
136      string basename;
137      string::size_type last_slash = file->name().find_last_of('/');
138      if (last_slash == string::npos) {
139        basename = file->name();
140      } else {
141        basename = file->name().substr(last_slash + 1);
142      }
143      name = UnderscoresToCamelCaseImpl(StripProto(basename), true);
144    }
145  }
146
147  return name;
148}
149
150string FileJavaPackage(const Params& params, const FileDescriptor* file) {
151  if (params.has_java_package(file->name())) {
152    return params.java_package(file->name());
153  } else {
154    string result = kDefaultPackage;
155    if (!file->package().empty()) {
156      if (!result.empty()) result += '.';
157      result += file->package();
158    }
159    return result;
160  }
161}
162
163string ToJavaName(const Params& params, const string& full_name,
164    const FileDescriptor* file) {
165  string result;
166  if (params.java_multiple_files()) {
167    result = FileJavaPackage(params, file);
168  } else {
169    result = ClassName(params, file);
170  }
171  if (file->package().empty()) {
172    result += '.';
173    result += full_name;
174  } else {
175    // Strip the proto package from full_name since we've replaced it with
176    // the Java package. If there isn't an outer classname then strip it too.
177    int sizeToSkipPackageName = file->package().size();
178    int sizeToSkipOutClassName;
179    if (params.has_java_outer_classname(file->name())) {
180      sizeToSkipOutClassName = 0;
181    } else {
182      sizeToSkipOutClassName =
183                full_name.find_first_of('.', sizeToSkipPackageName + 1);
184    }
185    int sizeToSkip = sizeToSkipOutClassName > 0 ?
186            sizeToSkipOutClassName : sizeToSkipPackageName;
187    string class_name = full_name.substr(sizeToSkip + 1);
188    if (class_name == FileClassName(params, file)) {
189      // Done class_name is already present.
190    } else {
191      result += '.';
192      result += class_name;
193    }
194  }
195  return result;
196}
197
198string ClassName(const Params& params, const FileDescriptor* descriptor) {
199  string result = FileJavaPackage(params, descriptor);
200  if (!result.empty()) result += '.';
201  result += FileClassName(params, descriptor);
202  return result;
203}
204
205string ClassName(const Params& params, const EnumDescriptor* descriptor) {
206  string result;
207  const FileDescriptor* file = descriptor->file();
208  const string file_name = file->name();
209  const string full_name = descriptor->full_name();
210
211  // Remove enum class name as we use int's for enums
212  string base_name = full_name.substr(0, full_name.find_last_of('.'));
213
214  if (!file->package().empty()) {
215    if (file->package() == base_name.substr(0, file->package().size())) {
216      // Remove package name leaving just the parent class of the enum
217      int offset = file->package().size();
218      if (base_name.size() > offset) {
219        // Remove period between package and class name if there is a classname
220        offset += 1;
221      }
222      base_name = base_name.substr(offset);
223    } else {
224      GOOGLE_LOG(FATAL) << "Expected package name to start an enum";
225    }
226  }
227
228  // Construct the path name from the package and outer class
229
230  // Add the java package name if it exsits
231  if (params.has_java_package(file_name)) {
232    result += params.java_package(file_name);
233  }
234
235  // Add the outer classname if it exists
236  if (params.has_java_outer_classname(file_name)) {
237    if (!result.empty()) {
238      result += ".";
239    }
240    result += params.java_outer_classname(file_name);
241  }
242
243  // Create the full class name from the base and path
244  if (!base_name.empty()) {
245    if (!result.empty()) {
246      result += ".";
247    }
248    result += base_name;
249  }
250  return result;
251}
252
253string FieldConstantName(const FieldDescriptor *field) {
254  string name = field->name() + "_FIELD_NUMBER";
255  UpperString(&name);
256  return name;
257}
258
259JavaType GetJavaType(FieldDescriptor::Type field_type) {
260  switch (field_type) {
261    case FieldDescriptor::TYPE_INT32:
262    case FieldDescriptor::TYPE_UINT32:
263    case FieldDescriptor::TYPE_SINT32:
264    case FieldDescriptor::TYPE_FIXED32:
265    case FieldDescriptor::TYPE_SFIXED32:
266      return JAVATYPE_INT;
267
268    case FieldDescriptor::TYPE_INT64:
269    case FieldDescriptor::TYPE_UINT64:
270    case FieldDescriptor::TYPE_SINT64:
271    case FieldDescriptor::TYPE_FIXED64:
272    case FieldDescriptor::TYPE_SFIXED64:
273      return JAVATYPE_LONG;
274
275    case FieldDescriptor::TYPE_FLOAT:
276      return JAVATYPE_FLOAT;
277
278    case FieldDescriptor::TYPE_DOUBLE:
279      return JAVATYPE_DOUBLE;
280
281    case FieldDescriptor::TYPE_BOOL:
282      return JAVATYPE_BOOLEAN;
283
284    case FieldDescriptor::TYPE_STRING:
285      return JAVATYPE_STRING;
286
287    case FieldDescriptor::TYPE_BYTES:
288      return JAVATYPE_BYTES;
289
290    case FieldDescriptor::TYPE_ENUM:
291      return JAVATYPE_ENUM;
292
293    case FieldDescriptor::TYPE_GROUP:
294    case FieldDescriptor::TYPE_MESSAGE:
295      return JAVATYPE_MESSAGE;
296
297    // No default because we want the compiler to complain if any new
298    // types are added.
299  }
300
301  GOOGLE_LOG(FATAL) << "Can't get here.";
302  return JAVATYPE_INT;
303}
304
305const char* BoxedPrimitiveTypeName(JavaType type) {
306  switch (type) {
307    case JAVATYPE_INT    : return "java.lang.Integer";
308    case JAVATYPE_LONG   : return "java.lang.Long";
309    case JAVATYPE_FLOAT  : return "java.lang.Float";
310    case JAVATYPE_DOUBLE : return "java.lang.Double";
311    case JAVATYPE_BOOLEAN: return "java.lang.Boolean";
312    case JAVATYPE_STRING : return "java.lang.String";
313    case JAVATYPE_BYTES  : return "com.google.protobuf.micro.ByteStringMicro";
314    case JAVATYPE_ENUM   : return "java.lang.Integer";
315    case JAVATYPE_MESSAGE: return NULL;
316
317    // No default because we want the compiler to complain if any new
318    // JavaTypes are added.
319  }
320
321  GOOGLE_LOG(FATAL) << "Can't get here.";
322  return NULL;
323}
324
325bool AllAscii(const string& text) {
326  for (int i = 0; i < text.size(); i++) {
327    if ((text[i] & 0x80) != 0) {
328      return false;
329    }
330  }
331  return true;
332}
333
334string DefaultValue(const Params& params, const FieldDescriptor* field) {
335  // Switch on cpp_type since we need to know which default_value_* method
336  // of FieldDescriptor to call.
337  switch (field->cpp_type()) {
338    case FieldDescriptor::CPPTYPE_INT32:
339      return SimpleItoa(field->default_value_int32());
340    case FieldDescriptor::CPPTYPE_UINT32:
341      // Need to print as a signed int since Java has no unsigned.
342      return SimpleItoa(static_cast<int32>(field->default_value_uint32()));
343    case FieldDescriptor::CPPTYPE_INT64:
344      return SimpleItoa(field->default_value_int64()) + "L";
345    case FieldDescriptor::CPPTYPE_UINT64:
346      return SimpleItoa(static_cast<int64>(field->default_value_uint64())) +
347             "L";
348    case FieldDescriptor::CPPTYPE_DOUBLE: {
349     double value = field->default_value_double();
350      if (value == numeric_limits<double>::infinity()) {
351        return "Double.POSITIVE_INFINITY";
352      } else if (value == -numeric_limits<double>::infinity()) {
353        return "Double.NEGATIVE_INFINITY";
354      } else if (value != value) {
355        return "Double.NaN";
356      } else {
357        return SimpleDtoa(value) + "D";
358      }
359    }
360    case FieldDescriptor::CPPTYPE_FLOAT: {
361      float value = field->default_value_float();
362      if (value == numeric_limits<float>::infinity()) {
363        return "Float.POSITIVE_INFINITY";
364      } else if (value == -numeric_limits<float>::infinity()) {
365        return "Float.NEGATIVE_INFINITY";
366      } else if (value != value) {
367        return "Float.NaN";
368      } else {
369        return SimpleFtoa(value) + "F";
370      }
371    }
372    case FieldDescriptor::CPPTYPE_BOOL:
373      return field->default_value_bool() ? "true" : "false";
374    case FieldDescriptor::CPPTYPE_STRING:
375      if (field->type() == FieldDescriptor::TYPE_BYTES) {
376        if (field->has_default_value()) {
377          // See comments in Internal.java for gory details.
378          return strings::Substitute(
379            "com.google.protobuf.micro.ByteStringMicro.copyFromUtf8(\"$0\")",
380            CEscape(field->default_value_string()));
381        } else {
382          return "com.google.protobuf.micro.ByteStringMicro.EMPTY";
383        }
384      } else {
385        if (AllAscii(field->default_value_string())) {
386          // All chars are ASCII.  In this case CEscape() works fine.
387          return "\"" + CEscape(field->default_value_string()) + "\"";
388        } else {
389          // See comments in Internal.java for gory details.
390          // BUG: Internal NOT SUPPORTED need to fix!!
391          return strings::Substitute(
392            "com.google.protobuf.micro.Internal.stringDefaultValue(\"$0\")",
393            CEscape(field->default_value_string()));
394        }
395      }
396
397    case FieldDescriptor::CPPTYPE_ENUM:
398      return ClassName(params, field->enum_type()) + "." +
399             field->default_value_enum()->name();
400
401    case FieldDescriptor::CPPTYPE_MESSAGE:
402      return ClassName(params, field->message_type()) + ".getDefaultInstance()";
403
404    // No default because we want the compiler to complain if any new
405    // types are added.
406  }
407
408  GOOGLE_LOG(FATAL) << "Can't get here.";
409  return "";
410}
411
412}  // namespace javamicro
413}  // namespace compiler
414}  // namespace protobuf
415}  // namespace google
416