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