Classfile API proposal to integrate basic print functionality directly to ClassModel and MethodModel

Brian Goetz brian.goetz at oracle.com
Tue Jul 26 19:43:03 UTC 2022


> To be more specific about the actual classes:
>
> Mapping accepts only fragments, because it must render as single line 
> and for example in XML it renders as single element with attributes.
>

OK, so this unearths a previously unstated requirement: that we be able 
to turn certain maps into attributes, rather than embedded elements, 
when converting to XML.  Does this requirement correspond to anything in 
other formats?  Do we gain anything by dropping this, and formatting 
always to elements, and/or using other heuristics to determine when we 
can get away with this format optimization?

In general, I'd like to unearth more of this sort of requirements. 
Looking at the current hierarchy, I can't help but feel we've not yet 
"hit bottom"; it doesn't feel factored into separate concerns yet.  But 
I'm optimistic we can get there.

Here's a naive decomposition, which looks a lot like the JSON spec if 
you squint:

     Value = SimpleValue(ConstantDesc) | ListValue(List<Value>) | 
MapValue(Map<String, Value>)

Converting to this form is useful separate from formatting -- it 
provides a basis for query/traversal using simple string keys.  But it's 
not good enough for getting good formatting yet, because you need 
formatting hints to determine how to lay out / indent, right? So let's 
consider how we might add these in as hints, that are more transparent.

So far, I've seen that it's useful to:

  - Indent properly in multi-line formatting
  - Render simple maps in XML using attributes rather than elements
  - Render some lists on one line, rather than one line per element
  - Render some maps on one line, rather than one line per element

The single-line / multi-line seems to be a hint based on the 
"complexity" of the element, which the traverser knows and wants to 
encode in the result.  Here's an attempt at simplifying.

     Value = SimpleValue(ConstantDesc c) | ListValue(List<Value> l) | 
MapValue(Map<String, Value> m)
          | BlockValue(Value v)

Here, BlockValue is a "hint wrapper", which takes some Value and wraps 
it with a hint that "this thing is complex, don't try to do it all in 
one line."  Non-format code can just ignore the hint and unwrap the 
BlockValue payload, and keep going.

You currently distinguish the payload kind between Mapping and 
BlockMapping; one has a List<Fragment>, whereas the other has a 
List<Printable>.  If we detune the payload type to List<Printable> 
(Value in this simple example), we only need one Mapping type.  And the 
same thing goes with List vs BlockList.

This puts a small additional burden on the formatter -- dealing with the 
case where we have an unwrapped List (therefore, non-block formatting), 
but some of its elements are not simple values.  I think this is easy 
enough, and there are choices: either reject these, or we fall back to 
block formatting when non-simple values are present.  This is a simple 
matter of checking:

     l.stream().allMatch(v -> v instanceof SimpleValue)

and falling back to block formatting if this isn't true.

Alternately, we can get rid of the block wrapper, and add a "block hint" 
to the structured elements.  Switching back to your notation (but 
keeping my "keys are only for maps" modeling):

     Printable = Value(ConstantDesc value)
         | ValueList(FormatHint f, List<Printable> list)
         | ValueMap(FormatHint f, Map<String, Printable> map)
         | Comment()

where FormatHint might be as simple as `enum FormatHint { BLOCK, 
NOT_BLOCK }`, but could of course be fancier.

When we lose the specificity of the List element type, we downgrade the 
formatting metadata from "authoritative requirement" to "hint", but that 
seems OK, since these are heuristics for optimizing the human-readable 
output.

If someone arrived at this ADT, I think they'd know immediately what it 
means.  That's a big plus.

Would this work?




> It can hold single values or list of values, where the list is 
> rendered into a single attribute value.
>
> It cannot hold another Mapping because we cannot embed XML element 
> into an attribute (so that is we it implements printable).
>
> Rendering multiple elements on a single line is still valid XML 
> document, however far from human readability.
>
> Mapping can represent a Fragment in JSON and in YAML, however XML 
> throws an axe into that possibility.
>
> BlockMapping is the most powerful (multi-line indenting) joint, able 
> to nest and render correctly another BlockMapping as well as any other 
> Printable or Fragment.
>
> ValueList is restricted to leaf values (Strings, quoted String, 
> numbers), because it is simple in all three formats. Construction of 
> generic List of Fragments will require printers to render tons of 
> other joint combinations, which we simply do not need.
>
> BlockList makes sense only in combination with BlockMappings. We would 
> like to avoid BlockList of BlockLists as multi-level unnamed lists do 
> not make much sense. Also rendering lists with any other joints is 
> useless. If you have list of classes, list of methods, or list of 
> fields – it does not make sense to put any other Fragment, BlockList 
> or Comment in between them. Theoretically we can replace BlockList 
> with ListOfBlockMaps(String key, String mapsKey, List<List<Printable>> 
> printables), however that does not make anything easier nor smaller.
>
> *From: *Brian Goetz <brian.goetz at oracle.com>
> *Date: *Monday, 25 July 2022 19:59
> *To: *Adam Sotona <adam.sotona at oracle.com>
> *Cc: *classfile-api-dev at openjdk.org <classfile-api-dev at openjdk.org>
> *Subject: *Re: Classfile API proposal to integrate basic print 
> functionality directly to ClassModel and MethodModel
>
> If "block" means multi-line, and non-block means single-line, I'm 
> confused as to why we have
>
>    public record Mapping(String key, List<Fragment> fragments) 
> implements Printable {}
> but
>    public record BlockMapping(String key, List<Printable> printables) 
> implements Printable {}
>
> where one has fragments and the other only has Printables. Similarly, 
> we have
>
>     public record ValueList(String key, List<ConstantDesc> values) 
> implements Fragment {}
>     public record BlockList(String key, List<BlockMapping> 
> blockMappings) implements Printable {}
>
> where again the List element differs.   Is there no way to make the 
> block/non-block orthogonal to the payload type?  That would allow us 
> to replace the two BlockXxx(...) with different payloads, with a 
> Block<T> wrapper as a formatting hint.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20220726/e653e83c/attachment.htm>


More information about the classfile-api-dev mailing list