1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "java_proto_stream_code_generator.h"
18
19 #include <stdio.h>
20
21 #include <algorithm>
22 #include <iomanip>
23 #include <iostream>
24 #include <map>
25 #include <sstream>
26 #include <string>
27 #include <unordered_set>
28
29 #include "Errors.h"
30
31 using namespace android::stream_proto;
32 using namespace google::protobuf::io;
33 using namespace std;
34
outer_class_name_clashes_with_any_message(const string & outer_class_name,const vector<DescriptorProto> & messages)35 static bool outer_class_name_clashes_with_any_message(const string& outer_class_name,
36 const vector<DescriptorProto>& messages) {
37 return any_of(messages.cbegin(), messages.cend(), [&](const DescriptorProto& message) {
38 return message.name() == outer_class_name;
39 });
40 }
41
42 /**
43 * If the descriptor gives us a class name, use that. Otherwise make one up from
44 * the filename of the .proto file.
45 */
make_outer_class_name(const FileDescriptorProto & file_descriptor,const vector<DescriptorProto> & messages)46 static string make_outer_class_name(const FileDescriptorProto& file_descriptor,
47 const vector<DescriptorProto>& messages) {
48 string name = file_descriptor.options().java_outer_classname();
49 if (!name.empty()) {
50 return name;
51 }
52
53 // Outer class and messages with the same name would result in invalid java (outer class and
54 // inner class cannot have same names).
55 // If the outer class name clashes with any message, let's append an "OuterClass" suffix.
56 // This behavior is consistent with the standard protoc.
57 name = to_camel_case(file_base_name(file_descriptor.name()));
58 while (outer_class_name_clashes_with_any_message(name, messages)) {
59 name += "OuterClass";
60 }
61
62 if (name.empty()) {
63 ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unable to make an outer class name for file: %s",
64 file_descriptor.name().c_str());
65 name = "Unknown";
66 }
67
68 return name;
69 }
70
71 /**
72 * Figure out the package name that we are generating.
73 */
make_java_package(const FileDescriptorProto & file_descriptor)74 static string make_java_package(const FileDescriptorProto& file_descriptor) {
75 if (file_descriptor.options().has_java_package()) {
76 return file_descriptor.options().java_package();
77 } else {
78 return file_descriptor.package();
79 }
80 }
81
82 /**
83 * Figure out the name of the file we are generating.
84 */
make_file_name(const FileDescriptorProto & file_descriptor,const string & class_name)85 static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
86 string const package = make_java_package(file_descriptor);
87 string result;
88 if (package.size() > 0) {
89 result = replace_string(package, '.', '/');
90 result += '/';
91 }
92
93 result += class_name;
94 result += ".java";
95
96 return result;
97 }
98
indent_more(const string & indent)99 static string indent_more(const string& indent) {
100 return indent + INDENT;
101 }
102
103 /**
104 * Write the constants for an enum.
105 */
write_enum(stringstream & text,const EnumDescriptorProto & enu,const string & indent)106 static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
107 const int N = enu.value_size();
108 text << indent << "// enum " << enu.name() << endl;
109 for (int i = 0; i < N; i++) {
110 const EnumValueDescriptorProto& value = enu.value(i);
111 text << indent << "public static final int " << make_constant_name(value.name()) << " = "
112 << value.number() << ";" << endl;
113 }
114 text << endl;
115 }
116
117 /**
118 * Write a field.
119 */
write_field(stringstream & text,const FieldDescriptorProto & field,const string & indent)120 static void write_field(stringstream& text, const FieldDescriptorProto& field,
121 const string& indent) {
122 string optional_comment =
123 field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
124 string repeated_comment =
125 field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
126 string proto_type = get_proto_type(field);
127 string packed_comment = field.options().packed() ? " [packed=true]" : "";
128 text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
129 << field.name() << " = " << field.number() << packed_comment << ';' << endl;
130
131 text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
132
133 ios::fmtflags fmt(text.flags());
134 text << setfill('0') << setw(16) << hex << get_field_id(field);
135 text.flags(fmt);
136
137 text << "L;" << endl;
138
139 text << endl;
140 }
141
142 /**
143 * Write a Message constants class.
144 */
write_message(stringstream & text,const DescriptorProto & message,const string & indent)145 static void write_message(stringstream& text, const DescriptorProto& message,
146 const string& indent) {
147 int N;
148 const string indented = indent_more(indent);
149
150 text << indent << "// message " << message.name() << endl;
151 text << indent << "public final class " << message.name() << " {" << endl;
152 text << endl;
153
154 // Enums
155 N = message.enum_type_size();
156 for (int i = 0; i < N; i++) {
157 write_enum(text, message.enum_type(i), indented);
158 }
159
160 // Nested classes
161 N = message.nested_type_size();
162 for (int i = 0; i < N; i++) {
163 write_message(text, message.nested_type(i), indented);
164 }
165
166 // Fields
167 N = message.field_size();
168 for (int i = 0; i < N; i++) {
169 write_field(text, message.field(i), indented);
170 }
171
172 // Extensions
173 N = message.extension_size();
174 for (int i = 0; i < N; i++) {
175 write_field(text, message.extension(i), indented);
176 }
177
178 text << indent << "}" << endl;
179 text << endl;
180 }
181
182 /**
183 * Write the contents of a file.
184 *
185 * If there are enums and generate_outer is false, invalid java code will be generated.
186 */
write_file(CodeGeneratorResponse * response,const FileDescriptorProto & file_descriptor,const string & filename,bool generate_outer,const vector<EnumDescriptorProto> & enums,const vector<DescriptorProto> & messages)187 static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
188 const string& filename, bool generate_outer,
189 const vector<EnumDescriptorProto>& enums,
190 const vector<DescriptorProto>& messages) {
191 stringstream text;
192
193 string const package_name = make_java_package(file_descriptor);
194 string const outer_class_name = make_outer_class_name(file_descriptor, messages);
195
196 text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
197 text << "// source: " << file_descriptor.name() << endl << endl;
198
199 if (package_name.size() > 0) {
200 if (package_name.size() > 0) {
201 text << "package " << package_name << ";" << endl;
202 text << endl;
203 }
204 }
205
206 // This bit of policy is android api rules specific: Raw proto classes
207 // must never be in the API
208 text << "/** @hide */" << endl;
209 // text << "@android.annotation.TestApi" << endl;
210
211 if (generate_outer) {
212 text << "public final class " << outer_class_name << " {" << endl;
213 text << endl;
214 }
215
216 size_t N;
217 const string indented = generate_outer ? indent_more("") : string();
218
219 N = enums.size();
220 for (size_t i = 0; i < N; i++) {
221 write_enum(text, enums[i], indented);
222 }
223
224 N = messages.size();
225 for (size_t i = 0; i < N; i++) {
226 write_message(text, messages[i], indented);
227 }
228
229 if (generate_outer) {
230 text << "}" << endl;
231 }
232
233 CodeGeneratorResponse::File* file_response = response->add_file();
234 file_response->set_name(filename);
235 file_response->set_content(text.str());
236 }
237
238 /**
239 * Write one file per class. Put all of the enums into the "outer" class.
240 */
write_multiple_files(CodeGeneratorResponse * response,const FileDescriptorProto & file_descriptor,const unordered_set<string> & messages_allowlist)241 static void write_multiple_files(CodeGeneratorResponse* response,
242 const FileDescriptorProto& file_descriptor,
243 const unordered_set<string>& messages_allowlist) {
244 // If there is anything to put in the outer class file, create one
245 if (file_descriptor.enum_type_size() > 0) {
246 vector<EnumDescriptorProto> enums;
247 int N = file_descriptor.enum_type_size();
248 for (int i = 0; i < N; i++) {
249 auto enum_full_name =
250 file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
251 if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
252 continue;
253 }
254 enums.push_back(file_descriptor.enum_type(i));
255 }
256
257 vector<DescriptorProto> messages;
258
259 if (messages_allowlist.empty() || !enums.empty()) {
260 write_file(response, file_descriptor,
261 make_file_name(file_descriptor,
262 make_outer_class_name(file_descriptor, messages)),
263 true, enums, messages);
264 }
265 }
266
267 // For each of the message types, make a file
268 int N = file_descriptor.message_type_size();
269 for (int i = 0; i < N; i++) {
270 vector<EnumDescriptorProto> enums;
271
272 vector<DescriptorProto> messages;
273
274 auto message_full_name =
275 file_descriptor.package() + "." + file_descriptor.message_type(i).name();
276 if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
277 continue;
278 }
279 messages.push_back(file_descriptor.message_type(i));
280
281 if (messages_allowlist.empty() || !messages.empty()) {
282 write_file(response, file_descriptor,
283 make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
284 false, enums, messages);
285 }
286 }
287 }
288
write_single_file(CodeGeneratorResponse * response,const FileDescriptorProto & file_descriptor,const unordered_set<string> & messages_allowlist)289 static void write_single_file(CodeGeneratorResponse* response,
290 const FileDescriptorProto& file_descriptor,
291 const unordered_set<string>& messages_allowlist) {
292 int N;
293
294 vector<EnumDescriptorProto> enums;
295 N = file_descriptor.enum_type_size();
296 for (int i = 0; i < N; i++) {
297 auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
298 if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
299 continue;
300 }
301
302 enums.push_back(file_descriptor.enum_type(i));
303 }
304
305 vector<DescriptorProto> messages;
306 N = file_descriptor.message_type_size();
307 for (int i = 0; i < N; i++) {
308 auto message_full_name =
309 file_descriptor.package() + "." + file_descriptor.message_type(i).name();
310
311 if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
312 continue;
313 }
314
315 messages.push_back(file_descriptor.message_type(i));
316 }
317
318 if (messages_allowlist.empty() || !enums.empty() || !messages.empty()) {
319 write_file(response, file_descriptor,
320 make_file_name(file_descriptor,
321 make_outer_class_name(file_descriptor, messages)),
322 true, enums, messages);
323 }
324 }
325
parse_args_string(stringstream args_string_stream,unordered_set<string> & messages_allowlist_out)326 static void parse_args_string(stringstream args_string_stream,
327 unordered_set<string>& messages_allowlist_out) {
328 string line;
329 while (getline(args_string_stream, line, ';')) {
330 stringstream line_ss(line);
331 string arg_name;
332 getline(line_ss, arg_name, ':');
333 if (arg_name == "include_filter") {
334 string full_message_name;
335 while (getline(line_ss, full_message_name, ',')) {
336 messages_allowlist_out.insert(full_message_name);
337 }
338 } else {
339 ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
340 }
341 }
342 }
343
generate_java_protostream_code(CodeGeneratorRequest request)344 CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
345 CodeGeneratorResponse response;
346
347 unordered_set<string> messages_allowlist;
348 auto request_params = request.parameter();
349 if (!request_params.empty()) {
350 parse_args_string(stringstream(request_params), messages_allowlist);
351 }
352
353 // Build the files we need.
354 const int N = request.proto_file_size();
355 for (int i = 0; i < N; i++) {
356 const FileDescriptorProto& file_descriptor = request.proto_file(i);
357 if (should_generate_for_file(request, file_descriptor.name())) {
358 if (file_descriptor.options().java_multiple_files()) {
359 write_multiple_files(&response, file_descriptor, messages_allowlist);
360 } else {
361 write_single_file(&response, file_descriptor, messages_allowlist);
362 }
363 }
364 }
365
366 return response;
367 }
368