Skip to content

Commit 1ed5e42

Browse files
author
Karl Rieb
committed
Update Babel spec.
Fix struct class visibility errors caused by cross package imports.
1 parent 9d91e49 commit 1ed5e42

2 files changed

Lines changed: 140 additions & 36 deletions

File tree

generator/java.babelg.py

Lines changed: 139 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
213222
def 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+
233254
def 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

255328
class 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

Comments
 (0)