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

Brian Goetz brian.goetz at oracle.com
Thu Jul 21 17:23:23 UTC 2022


Let me propose an experiment: take the existing ClassPrinter implementation — still monolithic — and try refactoring to use internal “printable models”, where the keys in the output (e.g., “class name”) are derived from the keys in the printable model, rather than hard-coded format strings, and see how we like that?  That should be a small step, and if we like it, we can take another step.  If it turns out that is nastier to do that I am guessing, we can back off an think of another approach.

I’m not quite how it would look like.
This is one fragment of the actual templates in one format:
           new Block(",%n    \"module\":  {%n        \"name\": \"%s\",%n        \"flags\": %s,%n        \"version\": \"%s\",%n        \"uses\": %s", " }"),

Right.  So first, the words like “module” and “name” can be derived from the keys in the Map.  We have latitude here, but let’s say for example that the key is “class name” and in JSON this is rendered as “class name” and in XML as “<class-name>”.  These are both mechanical translations.  That gets all of the text out of the format, and carries the name in the Map instead. Note also that all of the format specifiers are %s, since we’ve already converted the leaves of the Map to String.  So much of these format strings can be generated from the data itself.

Second, in the code you have, there is an implicit alignment between the order of the arguments to “formatted” and the order of the format specifiers, as is natural for formatters.  But we can break this, by using a LinkedHashMap (which preserves order) for the keys to ensure they come out in the right order.

Third, the block/table structure (with its prefix and suffix) can be derived from the shape of the Map.   Instead of calling printTable, the key “requires” maps to a LIST of { “name” -> name, “flags” -> flags, “version” -> version }.  This alone is enough to trigger generation of an object whose key is “requires”, and whose corresponding value is an array of objects with name, flags, and version keys (and similar for other formats.).


            new Table(",%n        \"requires\": [", "]", "%n          { \"name\": \"%s\", \"flags\": %s, \"version\": \"%s\" }"),
            new Table(",%n        \"exports\": [", "]", "%n          { \"package\": \"%s\", \"flags\": %s, \"to\": %s }"),
            new Table(",%n        \"opens\": [", "]", "%n          { \"package\": \"%s\", \"flags\": %s, \"to\": %s }"),
            new Table(",%n        \"provides\": [", "]", "%n          { \"class\": \"%s\", \"with\": %s }"),
While applied are following way:

               case ModuleAttribute ma -> {

                    out.accept(template.module.header.formatted(ma.moduleName().name().stringValue(), quoteFlags(ma.moduleFlags()), ma.moduleVersion().map(Utf8Entry::stringValue).orElse(""), typesToString(ma.uses().stream().map(ce -> ce.asInternalName()))));

                    printTable(template.requires, ma.requires(), req -> new Object[] {req.requires().name().stringValue(), quoteFlags(req.requiresFlags()), req.requiresVersion().map(Utf8Entry::stringValue).orElse(null)});

                    printTable(template.exports, ma.exports(), exp -> new Object[] {exp.exportedPackage().name().stringValue(), quoteFlags(exp.exportsFlags()), typesToString(exp.exportsTo().stream().map(me -> me.name().stringValue()))});

                    printTable(template.opens, ma.opens(), open -> new Object[] {open.openedPackage().name().stringValue(), quoteFlags(open.opensFlags()), typesToString(open.opensTo().stream().map(me -> me.name().stringValue()))});

                    printTable(template.provides, ma.provides(), provide -> new Object[] {provide.provides().asInternalName(), typesToString(provide.providesWith().stream().map(me -> me.asInternalName()))});

                    out.accept(template.module.footer.formatted());

                }


Each individual parameter of each template has its position (the same position in each format) and format-specific escaping methods are frequently (and individually based on context) called. How do you suggest to pass it through generic key-value maps, when String format is index-based?

Control the order of keys in a Map with LHM; then just spool out the key-value pairs in order.



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20220721/3cb71359/attachment-0001.htm>


More information about the classfile-api-dev mailing list