@@ -210,6 +210,15 @@ def get_ancestors(data_type):
210210 return ancestors
211211
212212
213+ def get_underlying_type (data_type , allow_lists = True ):
214+ while True :
215+ if allow_lists and is_list_type (data_type ):
216+ data_type = data_type .data_type
217+ elif is_nullable_type (data_type ):
218+ data_type = data_type .data_type
219+ else :
220+ return data_type
221+
213222def get_routes_with_arg_type (data_type , allow_lists = True ):
214223 assert isinstance (data_type , DataTypeWrapper ), repr (data_type )
215224
@@ -218,18 +227,30 @@ def get_routes_with_arg_type(data_type, allow_lists=True):
218227 routes = []
219228 for babel_namespace in ctx .api .namespaces .values ():
220229 for babel_route in babel_namespace .routes :
221- arg_type = babel_route .arg_data_type
222- if allow_lists and is_list_type (arg_type ):
223- arg_type = arg_type .data_type
224- elif is_nullable_type (arg_type ):
225- arg_type = arg_type .data_type
230+ arg_type = get_underlying_type (babel_route .arg_data_type , allow_lists )
226231
227232 if arg_type == data_type .as_babel :
228233 routes .append (RouteWrapper (ctx , babel_namespace , babel_route ))
229234
230235 return routes
231236
232237
238+ def get_routes_with_result_type (data_type , allow_lists = True ):
239+ assert isinstance (data_type , DataTypeWrapper ), repr (data_type )
240+
241+ ctx = data_type ._ctx
242+
243+ routes = []
244+ for babel_namespace in ctx .api .namespaces .values ():
245+ for babel_route in babel_namespace .routes :
246+ result_type = get_underlying_type (babel_route .result_data_type , allow_lists )
247+
248+ if result_type == data_type .as_babel :
249+ routes .append (RouteWrapper (ctx , babel_namespace , babel_route ))
250+
251+ return routes
252+
253+
233254def get_fields_with_data_type (data_type ):
234255 assert isinstance (data_type , DataTypeWrapper ), repr (data_type )
235256
@@ -251,6 +272,58 @@ def get_fields_with_data_type(data_type):
251272
252273 return fields
253274
275+ def get_data_types_with_parent_type (data_type ):
276+ assert isinstance (data_type , DataTypeWrapper ), repr (data_type )
277+
278+ ctx = data_type ._ctx
279+
280+ babel_parent_type = data_type .as_babel
281+
282+ data_types = []
283+ for babel_namespace in ctx .api .namespaces .values ():
284+ for babel_data_type in babel_namespace .data_types :
285+ if babel_data_type .parent_type == babel_parent_type :
286+ data_types .append (DataTypeWrapper (ctx , babel_data_type ))
287+
288+ return data_types
289+
290+
291+ def is_data_type_required_outside_package (data_type ):
292+ # only structs can be flattened by routes. Unions must be passed explicitly as arguments.
293+ if not data_type .is_struct :
294+ return True
295+
296+ # we can hide structs if they are only used by a single route. In these cases, we make all
297+ # references to the struct point to the route instead. Since we flatten the struct fields into
298+ # the route method call, the users don't have to be aware that we use a POJO in the background.
299+ #
300+ # Note, if no routes use this data type, then it may be used elsewhere and we will have to make
301+ # it public for references. This only works when exactly 1 routes uses it as an argument.
302+ routes_with_arg = get_routes_with_arg_type (data_type )
303+ if len (routes_with_arg ) != 1 :
304+ return True
305+
306+ # make sure single route is in same namespace
307+ if routes_with_arg [0 ].namespace != data_type .namespace :
308+ return True
309+
310+ # if we return this type in any of our routes, it must be made available outside the package
311+ if get_routes_with_result_type (data_type ):
312+ return True
313+
314+ # Routes will flatten struct fields so callers don't have to pass in an instance of the
315+ # struct. However, we must verify the struct is not a field of some other struct.
316+ fields_with_type = get_fields_with_data_type (data_type )
317+ if fields_with_type :
318+ return True
319+
320+ # if any data types inherit form this data types, then make it public
321+ sub_data_types = get_data_types_with_parent_type (data_type )
322+ if sub_data_types :
323+ return True
324+
325+ return False
326+
254327
255328class GeneratorContext (object ):
256329 """
@@ -631,6 +704,15 @@ def babel_name(self):
631704 """
632705 return self ._babel_ent .name
633706
707+ @property
708+ def fq_babel_name (self ):
709+ """
710+ Fully-qualified Babel entity name.
711+
712+ :rtype: str
713+ """
714+ return '.' .join ((self .namespace .babel_name , self .babel_name ))
715+
634716 @property
635717 def java_class (self ):
636718 """
@@ -745,6 +827,10 @@ def babel_filename(self):
745827 """
746828 raise AssertionError ("use babel_filenames for namespaces" )
747829
830+ @property
831+ def fq_babel_name (self ):
832+ return self .babel_name
833+
748834 @property
749835 def data_types (self ):
750836 """
@@ -1373,6 +1459,10 @@ def __init__(self, ctx, containing_data_type, field):
13731459 super (FieldWrapper , self ).__init__ (ctx , containing_data_type .namespace , field )
13741460 self ._containing_babel_data_type = containing_data_type
13751461
1462+ @property
1463+ def fq_babel_name (self ):
1464+ return '.' .join ((self .namespace .babel_name , self .containing_data_type .babel_name , self .babel_name ))
1465+
13761466 @property
13771467 def babel_doc (self ):
13781468 doc = super (FieldWrapper , self ).babel_doc
@@ -1590,21 +1680,39 @@ def javadoc_ref_handler(self, tag, val, context=None):
15901680 return sanitize_javadoc (ref )
15911681
15921682 def translate_babel_doc (self , doc , context = None ):
1683+ if isinstance (doc , BabelWrapper ):
1684+ wrapper = doc
1685+ doc = wrapper .babel_doc
1686+ context = wrapper
1687+
15931688 if doc :
15941689 handler = lambda tag , val : self .javadoc_ref_handler (tag , val , context = context )
15951690 return self ._ctx .g .process_doc (sanitize_javadoc (doc ), handler )
15961691 else :
15971692 return doc
15981693
15991694 def generate_javadoc (self , doc , context = None , fields = (), params = (), returns = None , throws = (), deprecated = None , allow_defaults = True ):
1695+ # convenience for inferring various arguments from our wrapper objects
1696+ if isinstance (doc , BabelWrapper ):
1697+ wrapper = doc
1698+ doc = wrapper .babel_doc
1699+ context = context or wrapper
1700+
16001701 assert isinstance (doc , str ), repr (doc )
16011702 assert isinstance (context , BabelWrapper ) or context is None , repr (context )
16021703 assert isinstance (fields , (Sequence , types .GeneratorType )), repr (fields )
16031704 assert isinstance (params , (Sequence , types .GeneratorType , OrderedDict )), repr (params )
1604- assert isinstance (returns , str ) or returns is None , repr (returns )
1705+ assert isinstance (returns , ( str , BabelWrapper ) ) or returns is None , repr (returns )
16051706 assert isinstance (throws , (Sequence , types .GeneratorType , OrderedDict )), repr (throws )
16061707 assert isinstance (deprecated , (RouteWrapper , bool )) or deprecated is None , repr (deprecated )
16071708
1709+ # look at context to determine if we are deprecated, unless explicitly specified
1710+ if deprecated is None and context is not None :
1711+ if hasattr (context , "is_deprecated" ):
1712+ deprecated = context .is_deprecated
1713+ if deprecated and hasattr (context , "deprecated_by" ) and context .deprecated_by is not None :
1714+ deprecated = context .deprecated_by
1715+
16081716 params_doc = self .javadoc_params (fields , allow_defaults = allow_defaults )
16091717 params_doc .update (self ._translate_ordered_collection (params , context ))
16101718 returns_doc = self .translate_babel_doc (returns , context )
@@ -1916,13 +2024,14 @@ def _javadoc_field_ref(self, field):
19162024 containing_data_type = field .containing_data_type
19172025 if containing_data_type .is_struct :
19182026 routes = get_routes_with_arg_type (containing_data_type )
2027+
19192028 # we only handle cases where the struct appears as the argument to a single route
19202029 if len (routes ) == 1 :
1921- fields = get_fields_with_data_type (containing_data_type )
1922-
19232030 # the struct should not appear anywhere else besides as a route argument
1924- if not fields :
1925- return 'the {@code %s} argument to %s' % (field .java_name , self ._javadoc_route_ref (routes [0 ]))
2031+ return 'the {@code %s} argument to %s' % (field .java_name , self ._javadoc_route_ref (routes [0 ]))
2032+
2033+ # can't reference a package-private data type.
2034+ assert is_data_type_required_outside_package (containing_data_type ), field .fq_babel_name
19262035
19272036 # fallback to standard ref
19282037 if field .containing_data_type .is_enum :
@@ -2411,7 +2520,7 @@ def generate_route_base(self, route):
24112520 elif route .request_style == 'download' :
24122521 returns = "Downloader used to download the response body and view the server response."
24132522 elif route .has_result and (result_type .is_struct or result_type .is_union ):
2414- returns = result_type . babel_doc
2523+ returns = result_type
24152524 else :
24162525 returns = None
24172526
@@ -2440,8 +2549,8 @@ def generate_route_base(self, route):
24402549 signature = 'public %s %s() throws %s' % (return_type , route .java_method , throws )
24412550
24422551 out ('' )
2443- javadoc (route . babel_doc , context = route , returns = returns , deprecated = deprecated ,
2444- params = ((method_arg_name , arg_type . babel_doc ),) if not route .arg .is_void else ())
2552+ javadoc (route , returns = returns , deprecated = deprecated ,
2553+ params = ((method_arg_name , arg_type ),) if not route .arg .is_void else ())
24452554 with self .g .block (signature .strip ()):
24462555 if route .request_style == 'rpc' :
24472556 self .generate_route_rpc_call (route , method_arg_name )
@@ -2480,7 +2589,7 @@ def generate_route(self, route, required_only=True):
24802589 elif route .request_style == 'download' :
24812590 returns = "Downloader used to download the response body and view the server response."
24822591 elif route .has_result and (result_type .is_struct or result_type .is_union ):
2483- returns = result_type . babel_doc
2592+ returns = result_type
24842593 else :
24852594 returns = None
24862595
@@ -2511,7 +2620,7 @@ def generate_route(self, route, required_only=True):
25112620 )
25122621
25132622 out ('' )
2514- javadoc (doc , fields = fields , returns = returns , context = route , deprecated = route . deprecated_by , allow_defaults = False )
2623+ javadoc (doc , fields = fields , returns = returns , context = route , allow_defaults = False )
25152624 with self .g .block ('public %s %s(%s) throws %s' % (return_type , route .java_method , args , throws )):
25162625 arg_class = arg_type .java_type ()
25172626 required_args = ', ' .join (f .java_name for f in arg_type .all_required_fields )
@@ -2562,7 +2671,7 @@ def generate_route_builder(self, route):
25622671 args = ', ' .join (f .java_type_and_name () for f in required_fields )
25632672
25642673 out ('' )
2565- javadoc (route . babel_doc , fields = required_fields , returns = returns , context = route , deprecated = route . deprecated_by )
2674+ javadoc (route , fields = required_fields , returns = returns )
25662675 with self .g .block ('public %s %s(%s)' % (return_type , route .java_builder_method , args )):
25672676 builder_args = ', ' .join (f .java_name for f in required_fields )
25682677 out ('%s argBuilder = %s.newBuilder(%s);' % (
@@ -2665,7 +2774,7 @@ def generate_data_type_enum(self, data_type):
26652774 javadoc = self .doc .generate_javadoc
26662775
26672776 out ('' )
2668- javadoc (data_type . babel_doc , context = data_type )
2777+ javadoc (data_type )
26692778 out ('@JsonSerialize(using=%s.Serializer.class)' % data_type .java_class .name )
26702779 out ('@JsonDeserialize(using=%s.Deserializer.class)' % data_type .java_class .name )
26712780 with self .g .block ('public enum %s' % data_type .java_class ):
@@ -2899,7 +3008,7 @@ def generate_enum_values(self, data_type):
28993008
29003009 all_fields = data_type .all_fields
29013010 for i , field in enumerate (all_fields ):
2902- javadoc (field . babel_doc , context = field , deprecated = field . is_deprecated )
3011+ javadoc (field )
29033012 comment = ''
29043013 if field .is_catch_all :
29053014 assert field .data_type .is_void , field .data_type
@@ -2954,7 +3063,7 @@ def generate_data_type_union(self, data_type):
29543063 for field in data_type .all_fields :
29553064 if not field .has_value :
29563065 singleton_args = ', ' .join (chain (("Tag.%s" % field .tag_name ,), nulls ))
2957- javadoc (field . babel_doc , context = field , deprecated = field . is_deprecated )
3066+ javadoc (field )
29583067 out ('public static final %s %s = new %s(%s);' % (
29593068 data_type .java_class ,
29603069 field .java_singleton , data_type .java_class ,
@@ -2979,8 +3088,7 @@ def generate_data_type_union(self, data_type):
29793088 for field in data_type .all_fields if field .has_value ),
29803089 ))
29813090 out ('' )
2982- javadoc (data_type .babel_doc ,
2983- context = data_type ,
3091+ javadoc (data_type ,
29843092 fields = (f for f in data_type .all_fields if f .has_value ),
29853093 params = OrderedDict (tag = "Discriminating tag for this instance." ))
29863094 with self .g .block ('private %s(%s)' % (data_type .java_class , args )):
@@ -3153,7 +3261,6 @@ def generate_data_type_union_field_methods(self, data_type):
31533261 params = OrderedDict (value = "value to assign to this instance." ) if field .has_value else (),
31543262 returns = returns ,
31553263 throws = self .doc .javadoc_throws (field , "value" ),
3156- deprecated = field .is_deprecated ,
31573264 )
31583265 if field .has_value :
31593266 with self .g .block ('public static %s %s(%s value)' % (
@@ -3170,7 +3277,7 @@ def generate_data_type_union_field_methods(self, data_type):
31703277
31713278 if field .data_type .is_nullable :
31723279 out ('' )
3173- javadoc (doc , context = field , returns = returns , deprecated = field . is_deprecated )
3280+ javadoc (doc , context = field , returns = returns )
31743281 with self .g .block ('public static %s %s()' % (data_type .java_class , field .java_factory_method )):
31753282 out ('return %s(null);' % field .java_factory_method )
31763283
@@ -3191,8 +3298,7 @@ def generate_data_type_union_field_methods(self, data_type):
31913298 """ % (self .doc .javadoc_ref (field ), field .java_is_union_type_method ),
31923299 throws = OrderedDict (
31933300 IllegalStateException = "If {@link #%s} is {@code false}." % field .java_is_union_type_method ,
3194- ),
3195- deprecated = field .is_deprecated ,
3301+ )
31963302 )
31973303 with self .g .block ('public %s %s()' % (field .java_type (), field .java_getter )):
31983304 with self .g .block ('if (this.tag != Tag.%s)' % field .tag_name ):
@@ -3211,16 +3317,14 @@ def generate_data_type_struct(self, data_type):
32113317 #
32123318 # The exception to this rule are struct classes located in namespaces outside the route
32133319 # namespace. To be able to import these classes, they will have to remain public.
3214- visibility = 'public'
3215- route_refs = get_routes_with_arg_type (data_type , allow_lists = False )
3216- if route_refs and all (r .namespace == data_type .namespace for r in route_refs ):
3217- field_refs = get_fields_with_data_type (data_type )
3218- if not field_refs :
3219- # package private since this struct only gets used privately as a route arg.
3220- visibility = ''
3320+ if is_data_type_required_outside_package (data_type ):
3321+ visibility = 'public'
3322+ else :
3323+ # package private since this struct only gets used privately as a route arg.
3324+ visibility = ''
32213325
32223326 out ('' )
3223- javadoc (data_type . babel_doc , context = data_type )
3327+ javadoc (data_type )
32243328 out ('@JsonSerialize(using=%s.Serializer.class)' % data_type .java_class .name )
32253329 out ('@JsonDeserialize(using=%s.Deserializer.class)' % data_type .java_class .name )
32263330 with self .g .block (('%s class %s' % (visibility , data_type .java_class_with_inheritance )).strip ()):
@@ -3308,7 +3412,7 @@ def generate_data_type_struct(self, data_type):
33083412 if field .has_default :
33093413 returns += ' Defaults to %s.' % field .default_value
33103414
3311- javadoc (field . babel_doc , context = field , returns = returns , deprecated = field . is_deprecated )
3415+ javadoc (field , returns = returns )
33123416 with self .g .block ('public %s %s()' % (field .java_type (), field .java_getter )):
33133417 out ('return %s;' % field .java_name )
33143418
@@ -3433,7 +3537,7 @@ def generate_builder_methods(self, builder_class, fields, wrapped_builder_name=N
34333537 # withFieldName(FieldType fieldValue);
34343538 #
34353539 out ('' )
3436- javadoc (doc , context = field , fields = (field ,), returns = 'this builder' , deprecated = field . is_deprecated )
3540+ javadoc (doc , context = field , fields = (field ,), returns = 'this builder' )
34373541 with self .g .block ('public %s %s(%s %s)' % (
34383542 builder_class ,
34393543 field .java_builder_setter ,
0 commit comments