protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc
Go to the documentation of this file.
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
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 <google/protobuf/compiler/cpp/cpp_enum.h>
36 
37 #include <cstdint>
38 #include <limits>
39 #include <map>
40 
41 #include <google/protobuf/compiler/cpp/cpp_helpers.h>
43 #include <google/protobuf/io/printer.h>
44 #include <google/protobuf/stubs/strutil.h>
45 
46 namespace google {
47 namespace protobuf {
48 namespace compiler {
49 namespace cpp {
50 
51 namespace {
52 // The GOOGLE_ARRAYSIZE constant is the max enum value plus 1. If the max enum value
53 // is kint32max, GOOGLE_ARRAYSIZE will overflow. In such cases we should omit the
54 // generation of the GOOGLE_ARRAYSIZE constant.
55 bool ShouldGenerateArraySize(const EnumDescriptor* descriptor) {
56  int32_t max_value = descriptor->value(0)->number();
57  for (int i = 0; i < descriptor->value_count(); i++) {
58  if (descriptor->value(i)->number() > max_value) {
59  max_value = descriptor->value(i)->number();
60  }
61  }
62  return max_value != std::numeric_limits<int32_t>::max();
63 }
64 
65 // Returns the number of unique numeric enum values. This is less than
66 // descriptor->value_count() when there are aliased values.
67 int CountUniqueValues(const EnumDescriptor* descriptor) {
68  std::set<int> values;
69  for (int i = 0; i < descriptor->value_count(); ++i) {
70  values.insert(descriptor->value(i)->number());
71  }
72  return values.size();
73 }
74 
75 } // namespace
76 
78  const std::map<std::string, std::string>& vars,
79  const Options& options)
81  classname_(ClassName(descriptor, false)),
83  generate_array_size_(ShouldGenerateArraySize(descriptor)),
84  variables_(vars) {
85  variables_["classname"] = classname_;
87  variables_["short_name"] = descriptor_->name();
88  variables_["nested_name"] = descriptor_->name();
89  variables_["resolved_name"] = ResolveKeyword(descriptor_->name());
90  variables_["prefix"] =
91  (descriptor_->containing_type() == NULL) ? "" : classname_ + "_";
92 }
93 
95 
97  Formatter format(printer, variables_);
98  format("enum ${1$$classname$$}$ : int {\n", descriptor_);
99  format.Indent();
100 
101  const EnumValueDescriptor* min_value = descriptor_->value(0);
102  const EnumValueDescriptor* max_value = descriptor_->value(0);
103 
104  for (int i = 0; i < descriptor_->value_count(); i++) {
105  auto format_value = format;
106  format_value.Set("name", EnumValueName(descriptor_->value(i)));
107  // In C++, an value of -2147483648 gets interpreted as the negative of
108  // 2147483648, and since 2147483648 can't fit in an integer, this produces a
109  // compiler warning. This works around that issue.
110  format_value.Set("number", Int32ToString(descriptor_->value(i)->number()));
111  format_value.Set("deprecation",
113 
114  if (i > 0) format_value(",\n");
115  format_value("${1$$prefix$$name$$}$ $deprecation$= $number$",
116  descriptor_->value(i));
117 
118  if (descriptor_->value(i)->number() < min_value->number()) {
119  min_value = descriptor_->value(i);
120  }
121  if (descriptor_->value(i)->number() > max_value->number()) {
122  max_value = descriptor_->value(i);
123  }
124  }
125 
127  // For new enum semantics: generate min and max sentinel values equal to
128  // INT32_MIN and INT32_MAX
129  if (descriptor_->value_count() > 0) format(",\n");
130  format(
131  "$classname$_$prefix$INT_MIN_SENTINEL_DO_NOT_USE_ = "
132  "std::numeric_limits<$int32$>::min(),\n"
133  "$classname$_$prefix$INT_MAX_SENTINEL_DO_NOT_USE_ = "
134  "std::numeric_limits<$int32$>::max()");
135  }
136 
137  format.Outdent();
138  format("\n};\n");
139 
140  format(
141  "$dllexport_decl $bool $classname$_IsValid(int value);\n"
142  "constexpr $classname$ ${1$$prefix$$short_name$_MIN$}$ = "
143  "$prefix$$2$;\n"
144  "constexpr $classname$ ${1$$prefix$$short_name$_MAX$}$ = "
145  "$prefix$$3$;\n",
146  descriptor_, EnumValueName(min_value), EnumValueName(max_value));
147 
148  if (generate_array_size_) {
149  format(
150  "constexpr int ${1$$prefix$$short_name$_ARRAYSIZE$}$ = "
151  "$prefix$$short_name$_MAX + 1;\n\n",
152  descriptor_);
153  }
154 
156  format(
157  "$dllexport_decl $const ::$proto_ns$::EnumDescriptor* "
158  "$classname$_descriptor();\n");
159  }
160 
161  // The _Name and _Parse functions. The lite implementation is table-based, so
162  // we make sure to keep the tables hidden in the .cc file.
164  format("const std::string& $classname$_Name($classname$ value);\n");
165  }
166  // The _Name() function accepts the enum type itself but also any integral
167  // type.
168  format(
169  "template<typename T>\n"
170  "inline const std::string& $classname$_Name(T enum_t_value) {\n"
171  " static_assert(::std::is_same<T, $classname$>::value ||\n"
172  " ::std::is_integral<T>::value,\n"
173  " \"Incorrect type passed to function $classname$_Name.\");\n");
175  format(
176  " return ::$proto_ns$::internal::NameOfEnum(\n"
177  " $classname$_descriptor(), enum_t_value);\n");
178  } else {
179  format(
180  " return $classname$_Name(static_cast<$classname$>(enum_t_value));\n");
181  }
182  format("}\n");
183 
185  format(
186  "inline bool $classname$_Parse(\n"
187  " ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, $classname$* "
188  "value) "
189  "{\n"
190  " return ::$proto_ns$::internal::ParseNamedEnum<$classname$>(\n"
191  " $classname$_descriptor(), name, value);\n"
192  "}\n");
193  } else {
194  format(
195  "bool $classname$_Parse(\n"
196  " ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, $classname$* "
197  "value);\n");
198  }
199 }
200 
202  io::Printer* printer) {
203  Formatter format(printer, variables_);
204  format(
205  "template <> struct is_proto_enum< $classtype$> : ::std::true_type "
206  "{};\n");
208  format(
209  "template <>\n"
210  "inline const EnumDescriptor* GetEnumDescriptor< $classtype$>() {\n"
211  " return $classtype$_descriptor();\n"
212  "}\n");
213  }
214 }
215 
217  Formatter format(printer, variables_);
218  format("typedef $classname$ $resolved_name$;\n");
219 
220  for (int j = 0; j < descriptor_->value_count(); j++) {
221  std::string deprecated_attr =
223  format(
224  "$1$static constexpr $resolved_name$ ${2$$3$$}$ =\n"
225  " $classname$_$3$;\n",
226  deprecated_attr, descriptor_->value(j),
228  }
229 
230  format(
231  "static inline bool $nested_name$_IsValid(int value) {\n"
232  " return $classname$_IsValid(value);\n"
233  "}\n"
234  "static constexpr $resolved_name$ ${1$$nested_name$_MIN$}$ =\n"
235  " $classname$_$nested_name$_MIN;\n"
236  "static constexpr $resolved_name$ ${1$$nested_name$_MAX$}$ =\n"
237  " $classname$_$nested_name$_MAX;\n",
238  descriptor_);
239  if (generate_array_size_) {
240  format(
241  "static constexpr int ${1$$nested_name$_ARRAYSIZE$}$ =\n"
242  " $classname$_$nested_name$_ARRAYSIZE;\n",
243  descriptor_);
244  }
245 
247  format(
248  "static inline const ::$proto_ns$::EnumDescriptor*\n"
249  "$nested_name$_descriptor() {\n"
250  " return $classname$_descriptor();\n"
251  "}\n");
252  }
253 
254  format(
255  "template<typename T>\n"
256  "static inline const std::string& $nested_name$_Name(T enum_t_value) {\n"
257  " static_assert(::std::is_same<T, $resolved_name$>::value ||\n"
258  " ::std::is_integral<T>::value,\n"
259  " \"Incorrect type passed to function $nested_name$_Name.\");\n"
260  " return $classname$_Name(enum_t_value);\n"
261  "}\n");
262  format(
263  "static inline bool "
264  "$nested_name$_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name,\n"
265  " $resolved_name$* value) {\n"
266  " return $classname$_Parse(name, value);\n"
267  "}\n");
268 }
269 
270 void EnumGenerator::GenerateMethods(int idx, io::Printer* printer) {
271  Formatter format(printer, variables_);
273  format(
274  "const ::$proto_ns$::EnumDescriptor* $classname$_descriptor() {\n"
275  " ::$proto_ns$::internal::AssignDescriptors(&$desc_table$);\n"
276  " return $file_level_enum_descriptors$[$1$];\n"
277  "}\n",
278  idx);
279  }
280 
281  format(
282  "bool $classname$_IsValid(int value) {\n"
283  " switch (value) {\n");
284 
285  // Multiple values may have the same number. Make sure we only cover
286  // each number once by first constructing a set containing all valid
287  // numbers, then printing a case statement for each element.
288 
289  std::set<int> numbers;
290  for (int j = 0; j < descriptor_->value_count(); j++) {
292  numbers.insert(value->number());
293  }
294 
295  for (std::set<int>::iterator iter = numbers.begin(); iter != numbers.end();
296  ++iter) {
297  format(" case $1$:\n", Int32ToString(*iter));
298  }
299 
300  format(
301  " return true;\n"
302  " default:\n"
303  " return false;\n"
304  " }\n"
305  "}\n"
306  "\n");
307 
309  // In lite mode (where descriptors are unavailable), we generate separate
310  // tables for mapping between enum names and numbers. The _entries table
311  // contains the bulk of the data and is sorted by name, while
312  // _entries_by_number is sorted by number and just contains pointers into
313  // _entries. The two tables allow mapping from name to number and number to
314  // name, both in time logarithmic in the number of enum entries. This could
315  // probably be made faster, but for now the tables are intended to be simple
316  // and compact.
317  //
318  // Enums with allow_alias = true support multiple entries with the same
319  // numerical value. In cases where there are multiple names for the same
320  // number, we treat the first name appearing in the .proto file as the
321  // canonical one.
322  std::map<std::string, int> name_to_number;
323  std::map<int, std::string> number_to_canonical_name;
324  for (int i = 0; i < descriptor_->value_count(); i++) {
326  name_to_number.emplace(value->name(), value->number());
327  // The same number may appear with multiple names, so we use emplace() to
328  // let the first name win.
329  number_to_canonical_name.emplace(value->number(), value->name());
330  }
331 
332  format(
333  "static ::$proto_ns$::internal::ExplicitlyConstructed<std::string> "
334  "$classname$_strings[$1$] = {};\n\n",
335  CountUniqueValues(descriptor_));
336 
337  // We concatenate all the names for a given enum into one big string
338  // literal. If instead we store an array of string literals, the linker
339  // seems to put all enum strings for a given .proto file in the same
340  // section, which hinders its ability to strip out unused strings.
341  format("static const char $classname$_names[] =");
342  for (const auto& p : name_to_number) {
343  format("\n \"$1$\"", p.first);
344  }
345  format(";\n\n");
346 
347  format(
348  "static const ::$proto_ns$::internal::EnumEntry $classname$_entries[] "
349  "= {\n");
350  int i = 0;
351  std::map<int, int> number_to_index;
352  int data_index = 0;
353  for (const auto& p : name_to_number) {
354  format(" { {$classname$_names + $1$, $2$}, $3$ },\n", data_index,
355  p.first.size(), p.second);
356  if (number_to_canonical_name[p.second] == p.first) {
357  number_to_index.emplace(p.second, i);
358  }
359  ++i;
360  data_index += p.first.size();
361  }
362 
363  format(
364  "};\n"
365  "\n"
366  "static const int $classname$_entries_by_number[] = {\n");
367  for (const auto& p : number_to_index) {
368  format(" $1$, // $2$ -> $3$\n", p.second, p.first,
369  number_to_canonical_name[p.first]);
370  }
371  format(
372  "};\n"
373  "\n");
374 
375  format(
376  "const std::string& $classname$_Name(\n"
377  " $classname$ value) {\n"
378  " static const bool dummy =\n"
379  " ::$proto_ns$::internal::InitializeEnumStrings(\n"
380  " $classname$_entries,\n"
381  " $classname$_entries_by_number,\n"
382  " $1$, $classname$_strings);\n"
383  " (void) dummy;\n"
384  " int idx = ::$proto_ns$::internal::LookUpEnumName(\n"
385  " $classname$_entries,\n"
386  " $classname$_entries_by_number,\n"
387  " $1$, value);\n"
388  " return idx == -1 ? ::$proto_ns$::internal::GetEmptyString() :\n"
389  " $classname$_strings[idx].get();\n"
390  "}\n",
391  CountUniqueValues(descriptor_));
392  format(
393  "bool $classname$_Parse(\n"
394  " ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, $classname$* "
395  "value) "
396  "{\n"
397  " int int_value;\n"
398  " bool success = ::$proto_ns$::internal::LookUpEnumValue(\n"
399  " $classname$_entries, $1$, name, &int_value);\n"
400  " if (success) {\n"
401  " *value = static_cast<$classname$>(int_value);\n"
402  " }\n"
403  " return success;\n"
404  "}\n",
406  }
407 
408  if (descriptor_->containing_type() != NULL) {
409  std::string parent = ClassName(descriptor_->containing_type(), false);
410  // Before C++17, we must define the static constants which were
411  // declared in the header, to give the linker a place to put them.
412  // But MSVC++ pre-2015 and post-2017 (version 15.5+) insists that we not.
413  format(
414  "#if (__cplusplus < 201703) && "
415  "(!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))\n");
416 
417  for (int i = 0; i < descriptor_->value_count(); i++) {
418  format("constexpr $classname$ $1$::$2$;\n", parent,
420  }
421  format(
422  "constexpr $classname$ $1$::$nested_name$_MIN;\n"
423  "constexpr $classname$ $1$::$nested_name$_MAX;\n",
424  parent);
425  if (generate_array_size_) {
426  format("constexpr int $1$::$nested_name$_ARRAYSIZE;\n", parent);
427  }
428 
429  format(
430  "#endif // (__cplusplus < 201703) && "
431  "(!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))\n");
432  }
433 }
434 
435 } // namespace cpp
436 } // namespace compiler
437 } // namespace protobuf
438 } // namespace google
descriptor_
string_view descriptor_
Definition: elf.cc:154
http2_test_server.format
format
Definition: http2_test_server.py:118
google::protobuf::value
const Descriptor::ReservedRange value
Definition: bloaty/third_party/protobuf/src/google/protobuf/descriptor.h:1954
absl::str_format_internal::LengthMod::j
@ j
variables_
std::map< std::string, std::string > variables_
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc:518
google::protobuf::compiler::cpp::EnumGenerator::GenerateMethods
void GenerateMethods(int idx, io::Printer *printer)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc:264
false
#define false
Definition: setup_once.h:323
google::protobuf::compiler::cpp::EnumGenerator::options_
const Options & options_
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h:90
google::protobuf::EnumDescriptor::value_count
int value_count() const
options
double_dict options[]
Definition: capstone_test.c:55
testing::internal::string
::std::string string
Definition: bloaty/third_party/protobuf/third_party/googletest/googletest/include/gtest/internal/gtest-port.h:881
grpc::protobuf::io::Printer
GRPC_CUSTOM_PRINTER Printer
Definition: src/compiler/config.h:54
google::protobuf
Definition: bloaty/third_party/protobuf/benchmarks/util/data_proto2_to_proto3_util.h:12
google::protobuf::compiler::cpp::QualifiedClassName
std::string QualifiedClassName(const Descriptor *d, const Options &options)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc:320
google::protobuf::compiler::cpp::EnumGenerator::EnumGenerator
EnumGenerator(const EnumDescriptor *descriptor, const std::map< std::string, std::string > &vars, const Options &options)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc:73
xds_manager.p
p
Definition: xds_manager.py:60
google::protobuf::compiler::cpp::EnumGenerator::GenerateDefinition
void GenerateDefinition(io::Printer *printer)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc:92
google::protobuf::compiler::cpp::EnumValueName
std::string EnumValueName(const EnumValueDescriptor *enum_value)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc:425
iterator
const typedef MCPhysReg * iterator
Definition: MCRegisterInfo.h:27
google::protobuf::compiler::cpp::EnumGenerator::classname_
const std::string classname_
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h:89
google::protobuf::FileDescriptor::syntax
Syntax syntax() const
Definition: bloaty/third_party/protobuf/src/google/protobuf/descriptor.h:2175
google::protobuf::FileDescriptor::SYNTAX_PROTO3
@ SYNTAX_PROTO3
Definition: bloaty/third_party/protobuf/src/google/protobuf/descriptor.h:1394
google::protobuf::EnumDescriptor::name
const std::string & name() const
max
int max
Definition: bloaty/third_party/zlib/examples/enough.c:170
google::protobuf::compiler::cpp::EnumGenerator::GenerateSymbolImports
void GenerateSymbolImports(io::Printer *printer) const
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc:211
google::protobuf::EnumDescriptor::file
const FileDescriptor * file() const
google::protobuf::compiler::cpp::ResolveKeyword
std::string ResolveKeyword(const string &name)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc:409
google::protobuf::EnumValueDescriptor::number
int number() const
google::protobuf::EnumDescriptor::value
const EnumValueDescriptor * value(int index) const
EnumValueDescriptor
Definition: protobuf/php/ext/google/protobuf/def.c:63
google::protobuf::compiler::cpp::DeprecatedAttribute
std::string DeprecatedAttribute(const Options &options, bool deprecated)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.h:68
cpp
Definition: third_party/bloaty/third_party/googletest/googlemock/scripts/generator/cpp/__init__.py:1
EnumDescriptor
Definition: bloaty/third_party/protobuf/ruby/ext/google/protobuf_c/protobuf.h:143
google::protobuf::compiler::cpp::HasDescriptorMethods
bool HasDescriptorMethods(const FileDescriptor *file, const Options &options)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.h:368
setup.idx
idx
Definition: third_party/bloaty/third_party/capstone/bindings/python/setup.py:197
google::protobuf::compiler::cpp::EnumGenerator::GenerateGetEnumDescriptorSpecializations
void GenerateGetEnumDescriptorSpecializations(io::Printer *printer)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc:196
google::protobuf::compiler::cpp::Int32ToString
std::string Int32ToString(int number)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc:599
options_
DebugStringOptions options_
Definition: bloaty/third_party/protobuf/src/google/protobuf/descriptor.cc:2390
google::protobuf::EnumDescriptor::containing_type
const Descriptor * containing_type() const
google::protobuf::compiler::cpp::EnumGenerator::generate_array_size_
const bool generate_array_size_
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h:92
google::protobuf::compiler::cpp::EnumGenerator::descriptor_
const EnumDescriptor * descriptor_
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h:88
google::protobuf::compiler::cpp::EnumGenerator::~EnumGenerator
~EnumGenerator()
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc:90
values
std::array< int64_t, Size > values
Definition: abseil-cpp/absl/container/btree_benchmark.cc:608
google::protobuf::compiler::cpp::ClassName
std::string ClassName(const Descriptor *descriptor)
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc:302
iter
Definition: test_winkernel.cpp:47
EnumValueDescriptor::number
int32_t number
Definition: protobuf/php/ext/google/protobuf/def.c:66
int32_t
signed int int32_t
Definition: stdint-msvc2008.h:77
descriptor
static const char descriptor[1336]
Definition: certs.upbdefs.c:16
compiler
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/plugin.pb.cc:21
google
Definition: bloaty/third_party/protobuf/benchmarks/util/data_proto2_to_proto3_util.h:11
google::protobuf::compiler::cpp::EnumGenerator::variables_
std::map< std::string, std::string > variables_
Definition: bloaty/third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h:94
i
uint64_t i
Definition: abseil-cpp/absl/container/btree_benchmark.cc:230
framework.helpers.highlighter.Formatter
Formatter
Definition: highlighter.py:53
cpp_names.h


grpc
Author(s):
autogenerated on Fri May 16 2025 02:58:04