GraalVM Native Image – First Impressions

blank

Introduction

A native image is an operating system-specific executable file. You can build such an image for basically every application running on a Java virtual machine. This approach promises faster start-up times and lower resource consumption. This makes it appealing for serverless computing, auto-scaling platforms and command-line tools.

I gained some impressions of this GraalVM technology while developing a standalone command line tool for formatting PL/SQL and SQL code. In this blog post, I share some personal experiences and thoughts.

Starting Point

My starting point is an executable JAR.  I can run it from the command line via java -jar tvdformat.jar. The main class com.trivadis.plsql.formatter.TvdFormat calls a JavaScript format.js and passes all command line parameters to the JavaScript. Behind the scenes, Oracle’s parser and formatter which are part of SQLcl and SQL Developer do the heavy lifting.

It’s quite obvious that this Java application loads a lot of classes and resources dynamically. The GraalVM’s native image builder can identify such objects with the tracing agent. Using the agent is simple. You start the Java application with an additional parameter. The idea is to run the application long enough to detect all dynamically loaded classes and resources. Technically, the trace agent intercepts the calls involved in that dynamic loading process. It’s a best-effort approach. It cannot guarantee completeness.

The next command shows how I run the formatter with the tracing agent for a small PL/SQL project:

Calling tvdformat with tracing agent
java -agentlib:native-image-agent=config-output-dir=config \
     -jar tvdformat.jar $HOME/github/plscope-utils \
     xml=$HOME/github/trivadis/plsql-formatter-settings/settings/sql_developer/trivadis_advanced_format.xml \
     arbori=$HOME/github/trivadis/plsql-formatter-settings/settings/sql_developer/trivadis_custom_format.arbori

This command formats 56 files and the trace agent produces 6 JSON configuration files in the config directory.


jni-config.json

Configuration file for parameter -H:JNIConfigurationFiles. See documentation.

jni-config.json
[
{
  "name":"com.trivadis.plsql.formatter.TvdFormat",
  "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }]}
,
{
  "name":"java.lang.ClassLoader",
  "methods":[
    {"name":"getPlatformClassLoader","parameterTypes":[] }, 
    {"name":"loadClass","parameterTypes":["java.lang.String"] }
  ]}
,
{
  "name":"java.lang.String",
  "methods":[
    {"name":"lastIndexOf","parameterTypes":["int"] }, 
    {"name":"substring","parameterTypes":["int"] }
  ]}
,
{
  "name":"java.lang.System",
  "methods":[
    {"name":"getProperty","parameterTypes":["java.lang.String"] }, 
    {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }
  ]}
,
{
  "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"}
,
{
  "name":"org.graalvm.nativebridge.jni.JNIExceptionWrapperEntryPoints",
  "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]}
 
]


predefined-classes-config.json

Configuration file for parameter -H:PredefinedClassesConfigurationFiles.

predefined-classes-config.json
[
  {
    "type":"agent-extracted",
    "classes":[
    ]
  }
]


proxy-config.json

Configuration file for parameter -H:DynamicProxyConfigurationFiles. See documentation.

proxy-config.json
[
]


reflect-config.json

Configuration file for parameter --H:ReflectionConfigurationFiles. See documentation.

reflect-config.json
[
{
  "name":"oracle.dbtools.app.Format",
  "methods":[
    {"name":"breaksAfterComma","parameterTypes":[] }, 
    {"name":"breaksAfterConcat","parameterTypes":[] }, 
    {"name":"breaksAfterLogicalConjunction","parameterTypes":[] }, 
    {"name":"breaksBeforeComma","parameterTypes":[] }, 
    {"name":"breaksBeforeConcat","parameterTypes":[] }, 
    {"name":"breaksBeforeLogicalConjunction","parameterTypes":[] }, 
    {"name":"dontFormatNode","parameterTypes":["oracle.dbtools.parser.Parsed","java.util.Map"] }, 
    {"name":"identifiers","parameterTypes":["oracle.dbtools.parser.Parsed","java.util.Map"] }, 
    {"name":"indentConditions","parameterTypes":[] }
  ]}
,
{
  "name":"oracle.dbtools.app.Persist2XML",
  "methods":[{"name":"values","parameterTypes":["oracle.dbtools.parser.Parsed","java.util.Map"] }]}
 
]


resource-config.json

Configuration file for parameter -H:ResourceConfigurationFiles. See documentation.

resource-config.json
{
  "resources":{
  "includes":[
    {
      "pattern":"\\QMETA-INF/services/com.oracle.truffle.api.TruffleLanguage$Provider\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/com.oracle.truffle.api.instrumentation.TruffleInstrument$Provider\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/com.oracle.truffle.api.library.DefaultExportProvider\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/com.oracle.truffle.api.object.LayoutFactory\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/com.oracle.truffle.js.runtime.Evaluator\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/java.nio.file.spi.FileSystemProvider\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/javax.script.ScriptEngineFactory\\E"
    }, 
    {
      "pattern":"\\QMETA-INF/services/org.graalvm.polyglot.impl.AbstractPolyglotImpl\\E"
    }, 
    {
      "pattern":"\\Qcom/oracle/truffle/nfi/backend/libffi/LibFFILanguage.class\\E"
    }, 
    {
      "pattern":"\\Qformat.js\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/app/format.prg\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/app/persist2xml.prg\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/arbori/arbori.grammar\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/arbori/std.arbori\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/parser/js/js.grammar\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/parser/plsql/allRules.txt\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/parser/plsql/doc/frequencies.txt\\E"
    }, 
    {
      "pattern":"\\Qoracle/dbtools/parser/xml.grammar\\E"
    }
  ]},
  "bundles":[{
      "name":"oracle.dbtools.util.Messages"
    }]
}


serialization-config.json

Configuration file for parameter -H:SerializationConfigurationFiles. “The serialization support ensures constructors for classes are contained in a native image so that they can be deserialized in the first place”. See release notes of GraalVM 21.0.0.

serialization-config.json
[
]


Environment

The environment for this experiment was:

  • MacBook Pro (16-inch, 2021) with an Apple M1 Max chip and 64 GB memory running on macOS Monterey 12.0.1
  • GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05)
  • Apache Maven 3.8.3
  • SQLcl: Release 21.4.0.0 Production Build: 21.4.0.348.1716, installed in /usr/local/bin/sqlcl
  • Standalone PL/SQL & SQL Formatter at commit b4d26bd installed in $HOME/trivadis/plsql-formatter-settings
    • tvdformat.jar produced via mvn -DskipTests=true package in the standalone/target subdirectory
    • zip -d tvdformat-21.4.1-SNAPSHOT.jar "META-INF/native-image/*" to remove the native-image configuration files (they would be automatically used otherwise)
  • plscope-utils at commit 0687f5c installed in $HOME/github/plscope-utils

You find the configuration files used in this blog post in this Gist.

Building Image With Tracing Agent’s Config Files

Let’s try to build a native image with these configuration files.

Build native image (1)
$JAVA_HOME/bin/native-image \
-cp /usr/local/bin/sqlcl/lib/dbtools-common.jar:\
tvdformat-21.4.1-SNAPSHOT.jar \
-H:JNIConfigurationFiles=config/jni-config.json \
-H:PredefinedClassesConfigurationFiles=config/predefined-classes-config.json \
-H:DynamicProxyConfigurationFiles=config/proxy-config.json \
-H:ReflectionConfigurationFiles=config/reflect-config.json \
-H:ResourceConfigurationFiles=config/resource-config.json \
-H:SerializationConfigurationFiles=config/serialization-config.json \
-H:+ReportExceptionStackTraces \
--language:js \
-H:Class=com.trivadis.plsql.formatter.TvdFormat \
-H:Name=tvdformat

Here’s the console output:

Console output
[tvdformat:15574]    classlist:   3,420.59 ms,  0.96 GB
[tvdformat:15574]        (cap):   2,147.77 ms,  0.96 GB
[tvdformat:15574]        setup:   6,412.44 ms,  0.96 GB
[tvdformat:15574]     (clinit):   1,036.63 ms,  6.18 GB
[tvdformat:15574]   (typeflow):  16,832.01 ms,  6.18 GB
[tvdformat:15574]    (objects):  31,681.96 ms,  6.18 GB
[tvdformat:15574]   (features):  12,333.23 ms,  6.18 GB
[tvdformat:15574]     analysis:  63,801.37 ms,  6.18 GB
[tvdformat:15574]     universe:   3,216.66 ms,  6.18 GB
10971 method(s) included for runtime compilation
[tvdformat:15574]      (parse):  11,562.89 ms,  6.14 GB
[tvdformat:15574]     (inline):   6,112.92 ms,  7.19 GB
[tvdformat:15574]    (compile):  35,886.56 ms,  7.23 GB
[tvdformat:15574]      compile:  58,027.14 ms,  7.07 GB
[tvdformat:15574]        image:   5,407.78 ms,  7.07 GB
[tvdformat:15574]        write:   2,973.57 ms,  7.07 GB
[tvdformat:15574]      [total]: 147,632.30 ms,  7.07 GB
# Printing build artifacts to: /Users/phs/github/trivadis/plsql-formatter-settings/standalone/target/tvdformat.build_artifacts.txt

No error messages. Great. And what’s the size of the tvdformat executable? 115 MB. The --language:js parameter contributes about 98 MB, which includes probably a bit more than necessary.

Anyway, let’s run the native image.

Run tvdformat as native image
./tvdformat $HOME/github/plscope-utils \
xml=$HOME/github/trivadis/plsql-formatter-settings/settings/sql_developer/trivadis_advanced_format.xml \
arbori=$HOME/github/trivadis/plsql-formatter-settings/settings/sql_developer/trivadis_custom_format.arbori

This call produces the following console output:

Plaintext
Exception in thread "main" javax.script.ScriptException: org.graalvm.polyglot.PolyglotException: TypeError: Access to host class java.lang.String is not allowed or does not exist.
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.toScriptException(GraalJSScriptEngine.java:483)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:460)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:400)
	at com.trivadis.plsql.formatter.TvdFormat.run(TvdFormat.java:34)
	at com.trivadis.plsql.formatter.TvdFormat.main(TvdFormat.java:67)
Caused by: org.graalvm.polyglot.PolyglotException: TypeError: Access to host class java.lang.String is not allowed or does not exist.
	at <js>.:program(<eval>:23)
	at org.graalvm.polyglot.Context.eval(Context.java:379)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458)
	... 3 more

The String class is used on line 23 in format.js. We need to register Java classes used in JavaScript and extend the configuration accordingly.

Extending Reflection Configuration (1)

I reviewed the format.js  and the JavaScript callback functions in trivadis_custom_format.arbori and created an additional configuration file reflect-config2.json for all Java classes used in JavaScript.


reflect-config2.json

[
  {
    "name" : "com.trivadis.plsql.formatter.ScriptRunnerContext",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.io.BufferedWriter",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.io.File",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.io.PrintStream",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.lang.Class",
    "methods" : [
      {"name" : "getDeclaredField"}
    ]
  },
  {
    "name" : "java.lang.Integer",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.lang.reflect.Field",
    "methods" : [
      { "name" : "setAccessible"},
      { "name" : "get"}
    ]
  },
  {
    "name" : "java.lang.String",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.lang.StringBuilder",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.lang.System",
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.net.URI",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.nio.file.Files",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.nio.file.FileSystems",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.nio.file.Paths",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.ArrayList",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.Arrays",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.Collection",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.HashMap",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.HashMap$KeySet",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.HashSet",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.Iterator",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.regex.Matcher",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.regex.Pattern",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.stream.Collectors",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.stream.ReferencePipeline$Head",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.stream.Stream",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "java.util.TreeSet",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format$Breaks",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format$BreaksX2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format$Case",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format$FlowControl",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format$InlineComments",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Format$Space",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.app.Persist2XML",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.arbori.Program",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.arbori.SqlProgram",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.Lexer",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.LexerToken",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.Parsed",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.ParseNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.plsql.SqlEarley",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.plsql.SyntaxError",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.Substitutions",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.parser.Token",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.util.Logger",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  },
  {
    "name" : "oracle.dbtools.util.Service",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allDeclaredClasses": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true,
    "allPublicClasses": true
  }
]


Now we can build the native image with this additional configuration file.

Build native image (2)
$JAVA_HOME/bin/native-image \
-cp /usr/local/bin/sqlcl/lib/dbtools-common.jar:\
tvdformat-21.4.1-SNAPSHOT.jar \
-H:JNIConfigurationFiles=config/jni-config.json \
-H:PredefinedClassesConfigurationFiles=config/predefined-classes-config.json \
-H:DynamicProxyConfigurationFiles=config/proxy-config.json \
-H:ReflectionConfigurationFiles=config/reflect-config.json,config/reflect-config2.json \
-H:ResourceConfigurationFiles=config/resource-config.json \
-H:SerializationConfigurationFiles=config/serialization-config.json \
-H:+ReportExceptionStackTraces \
--language:js \
-H:Class=com.trivadis.plsql.formatter.TvdFormat \
-H:Name=tvdformat

The build completes without errors and produces a native image of 138 MB. 23 MB larger. Let’s run it.

The second run produces this console output:

Console output of tvdformat run (2)
Exception in thread "main" javax.script.ScriptException: org.graalvm.polyglot.PolyglotException: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface java.util.function.Predicate] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.toScriptException(GraalJSScriptEngine.java:483)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:460)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:400)
	at com.trivadis.plsql.formatter.TvdFormat.run(TvdFormat.java:34)
	at com.trivadis.plsql.formatter.TvdFormat.main(TvdFormat.java:67)

An excellent error message. We need to configure the class java.util.function.Predicate via -H:DynamicProxyConfigurationFiles. Let’s do this.

Extending Dynamic Proxy Configuration

For this blog post, I decided to create a second configuration file proxy-config2.json to distinguish it from the one generated by the trace agent.

proxy-config2.json
[
  ["java.util.function.Predicate"]
]

Let’s build the native image with this additional configuration file.

Build native image (3)
$JAVA_HOME/bin/native-image \
-cp /usr/local/bin/sqlcl/lib/dbtools-common.jar:\
tvdformat-21.4.1-SNAPSHOT.jar \
-H:JNIConfigurationFiles=config/jni-config.json \
-H:PredefinedClassesConfigurationFiles=config/predefined-classes-config.json \
-H:DynamicProxyConfigurationFiles=config/proxy-config.json,config/proxy-config2.json \
-H:ReflectionConfigurationFiles=config/reflect-config.json,config/reflect-config2.json \
-H:ResourceConfigurationFiles=config/resource-config.json \
-H:SerializationConfigurationFiles=config/serialization-config.json \
-H:+ReportExceptionStackTraces \
--language:js \
-H:Class=com.trivadis.plsql.formatter.TvdFormat \
-H:Name=tvdformat

The build completes without errors and produces a native image of 138 MB. The same size as before. Let’s run it.

The third run produces this console output:

Console output of tvdformat run (3)
Formatting file 1 of 56: /Users/phs/github/plscope-utils/README.md... done.
Formatting file 2 of 56: /Users/phs/github/plscope-utils/database/README.md... Exception in thread "main" javax.script.ScriptException: java.lang.Exception: java.lang.AssertionError: oracle.dbtools.arbori.ScriptException: java.lang.NumberFormatException: Cannot parse null string
	at oracle.dbtools.app.Format.format(Format.java:387)
	at <js>.formatMarkdownFile(<eval>:489)
	at <js>.formatFiles(<eval>:528)
	at <js>.run(<eval>:552)
	at <js>.:program(<eval>:617)
	at org.graalvm.polyglot.Context.eval(Context.java:379)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:400)
	at com.trivadis.plsql.formatter.TvdFormat.run(TvdFormat.java:34)
	at com.trivadis.plsql.formatter.TvdFormat.main(TvdFormat.java:67)
Caused by: java.lang.Exception: java.lang.AssertionError: oracle.dbtools.arbori.ScriptException: java.lang.NumberFormatException: Cannot parse null string
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.toScriptException(GraalJSScriptEngine.java:476)
	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:460)
	... 3 more

This is quite interesting. The embedded format.js works now. The first file README.md does not contain SQL text blocks. and therefore the formatter was not called and no error was reported. However, the formatter failed for the second file. What could be the reason for NumberFormatException: Cannot parse null string? – In this case the ParseNode class could not be loaded dynamically. To load this class successfully a lot of other classes are also required.

Extending Reflection Configuration (2)

Identifying all the dynamically loaded classes is not that simple. To debug the native image you can enable or add logging output, review the related source code or use a debugger. The native image debugger is an enterprise feature that is on the roadmap for the community edition. However, you still need to identify the reason for every single runtime exception. After adding the class to the configuration file you need to rebuild the native image and run it to detect the next exception. Doing this manually is really time-consuming.

Another approach is to register classes and their constructors, methods and fields programmatically using configuration with features. I’ve done that for a few chosen packages of the dbtools-common.jar that are part of the SQLcl installation. See source on GitHub.

For this blog post, I created an additional configuration file with 218 classes.


reflect-config3.json

[
  {
    "name": "oracle.dbtools.app.CompletionItem",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.CompletionItem$Type",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.CompletionList",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$3",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$Breaks",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$BreaksX2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$Case",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$FlowControl",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$InlineComments",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Format$Space",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Obfuscator",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Persist2XML",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.Rewrite",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.SqlCompleter$IProgram",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.SqlId",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.XML2Table",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.XML2Table$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.CallLink$State",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Dotted",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.FlowGraph$Debug",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Loc",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Loc$LocType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlException",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlException$Cause",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$CharFamily",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Collection",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$DateFamily",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Ellipsis",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Exception",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Function",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Function$ARGMATCH",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$NumericFamily",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Record",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Scalar",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$ScalarClass",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$ScalarType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$Scope",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.PlsqlType$TYPECLASS",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.RegressionTest$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjection",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjection$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjection$2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionAnalysisFailure",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionAnalysisFailure$SqlInjectionFailureTypes",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$Debug",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$ParameterMode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$PickTokens",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$Reduction",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$Returns",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$SubprgBodyState",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$Token",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionGraph$Tokens",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionWarningTest$Issue",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionWarningTest$Issue$Type",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SqlInjectionWarningTest$SqlInjectionAdvice",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Symbol",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Symbol$FunctionSym",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Symbol$ObjectSym",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.Symbol$VariableSym",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SymbolTable",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SymbolTable$RecordSym",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.SymbolTable$ScopeType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$ArgNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$Debug",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$DottedNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$ExprNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$FunctionNodeDeclaration",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$IdentifierNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$IdentifierNodeDeclaration",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$LHSNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$LiteralNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$ModeNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$MultiArgNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$MultiExprNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$MultiIdentifierNodeDeclaration",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$MultiNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$TokenNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$TypeNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.ValueNode$ValueType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.app.injection.VerifyValueNodeTypes$Debug",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.AggregatePredicate",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.AggregatePredicate$Type",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.AncestorDescendantNodes",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.AncestorDescendantNodes$Type",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.AncestorExpr",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Attribute",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.AttributeDefinitions",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.BindVar",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.ChildExpr",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.ChildNumRelation",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Column",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Composite",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.CompositeExpr",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.EqualExpr",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.False",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Head",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Header",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.IdentedPredicate",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.IndependentAttribute",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.JSFunc",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.MaterializedPredicate",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.NodeContent",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.NodeMatchingSrc",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.NodesWMatchingSrc",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Oper",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Parent",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.ParentChildNodes",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Position",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.PositionalRelation",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.PredRef",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Predicate",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program$2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program$Command",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program$IncludedPrg",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program$JavaScriptBlock",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Program$PredicateCmd",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.SameNodes",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.ScriptException",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Sibling",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.SqlProgram",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Tail",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.True",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.arbori.Tuple",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Cell",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.EBNF$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Earley",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Earley$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Earley$PredictedTerminals",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Grammar$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Grammar$2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.LeafNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.LexerRegressionTest$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Matriceable",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Matrix",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.ParseNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Parseable",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Parsed",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Parser",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Parser$EarleyCell",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Parser$EarleyCell$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Parser$Tuple",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.RecognizedRule",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.RuleTuple",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Token",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Visual",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Visual$1ScrollablePicture",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Visual$1ScrollablePicture$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Yelrae",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.Yelrae$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.dictionary.DictionaryEarley$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlEarley$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlParser",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlParser$Identifier",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlParser$Keyword",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlParser$Optional",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlParser$ParsedGrammarSymbol",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.html.HtmlParser$Sequence",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.js.JavaScript$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.json.Interpreter$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.json.JsonEarley$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.json.ResponseError",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.BasicSuggestedItem",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.BasicTabCol$Column",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.LazyNode",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.ParsedSql",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.ParsedSql$Language",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SqlEarley",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SqlEarley$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SqlRules$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.StackParser",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.StackParser$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SyntaxError",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SyntaxError$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SyntaxError$2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.SyntaxError$3",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.doc.DocURL",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.doc.DocURL$Manual",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.doc.HarvestDoc$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.plsql.doc.Substr",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.parser.translation.Translate$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.raptor.LazyIcon",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.Encodings$CharsetComparator",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.InputOutputStreams$EmptyStream",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.InputOutputStreams$NullOutputStream",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.InputOutputStreams$Uncloseable",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.KeyboardFocusLogger",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.LogHandler",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.LogHandler$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.Messages",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.Service$1",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.Service$2",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.Service$3",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.Service$CLEAN_TYPE",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.StreamCopy$EmptyStream",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.VacuousCallableStatement",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.VacuousParameterMetaData",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.VacuousPreparedStatement",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.VacuousStatement",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.VacuousStatement$VacuousResultSet",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.VacuousStatement$VacuousResultSet$VacuousResultSetMetaData",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.BASE64Encoding",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.Decoder",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.Encoder",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.Encoding",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.EncodingException",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.EncodingType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.HEXEncoding",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.MimeType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.NullEncoding",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.StorageType",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  },
  {
    "name": "oracle.dbtools.util.encoding.URIEncoding",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true,
    "allPublicConstructors": true,
    "allPublicMethods": true,
    "allPublicFields": true
  }
]


Let’s once more build the native image with an additional configuration file.

Build native image (4)
$JAVA_HOME/bin/native-image \
-cp /usr/local/bin/sqlcl/lib/dbtools-common.jar:\
tvdformat-21.4.1-SNAPSHOT.jar \
-H:JNIConfigurationFiles=config/jni-config.json \
-H:PredefinedClassesConfigurationFiles=config/predefined-classes-config.json \
-H:DynamicProxyConfigurationFiles=config/proxy-config.json,config/proxy-config2.json \
-H:ReflectionConfigurationFiles=\
config/reflect-config.json,config/reflect-config2.json,config/reflect-config3.json \
-H:ResourceConfigurationFiles=config/resource-config.json \
-H:SerializationConfigurationFiles=config/serialization-config.json \
-H:+ReportExceptionStackTraces \
--language:js \
-H:Class=com.trivadis.plsql.formatter.TvdFormat \
-H:Name=tvdformat

The build completes without errors and produces a native image of 145 MB. 7 MB larger than before. Let’s run it.

The fourth run produces this console output:

Console output of tvdformat run (4)
Formatting file 1 of 56: /Users/phs/github/plscope-utils/README.md... done.
Formatting file 2 of 56: /Users/phs/github/plscope-utils/database/README.md... done.
Formatting file 3 of 56: /Users/phs/github/plscope-utils/database/demo/demo_script/00_demo_readme.sql... done.
Formatting file 4 of 56: /Users/phs/github/plscope-utils/database/demo/demo_script/01_demo_plscope.sql... done.
Formatting file 5 of 56: /Users/phs/github/plscope-utils/database/demo/demo_script/02_demo_lineage.sql... done.
Formatting file 6 of 56: /Users/phs/github/plscope-utils/database/demo/demo_script/03_demo_utl_xml_parsequery.sql... done.
Formatting file 7 of 56: /Users/phs/github/plscope-utils/database/demo/package/etl.pkb... done.
Formatting file 8 of 56: /Users/phs/github/plscope-utils/database/demo/package/etl.pks... done.
Formatting file 9 of 56: /Users/phs/github/plscope-utils/database/demo/synonym/source_syn.sql... done.
Formatting file 10 of 56: /Users/phs/github/plscope-utils/database/demo/table/dept.sql... done.
Formatting file 11 of 56: /Users/phs/github/plscope-utils/database/demo/table/deptsal.sql... done.
Formatting file 12 of 56: /Users/phs/github/plscope-utils/database/demo/table/deptsal_err.sql... done.
Formatting file 13 of 56: /Users/phs/github/plscope-utils/database/demo/table/drop_demo_tables.sql... done.
Formatting file 14 of 56: /Users/phs/github/plscope-utils/database/demo/table/emp.sql... done.
Formatting file 15 of 56: /Users/phs/github/plscope-utils/database/demo/view/source_view.sql... done.
Formatting file 16 of 56: /Users/phs/github/plscope-utils/database/install.sql... done.
Formatting file 17 of 56: /Users/phs/github/plscope-utils/database/install_test.sql... done.
Formatting file 18 of 56: /Users/phs/github/plscope-utils/database/test/package/test_dd_util.pkb... done.
Formatting file 19 of 56: /Users/phs/github/plscope-utils/database/test/package/test_dd_util.pks... done.
Formatting file 20 of 56: /Users/phs/github/plscope-utils/database/test/package/test_etl.pkb... done.
Formatting file 21 of 56: /Users/phs/github/plscope-utils/database/test/package/test_etl.pks... done.
Formatting file 22 of 56: /Users/phs/github/plscope-utils/database/test/package/test_lineage_util.pkb... done.
Formatting file 23 of 56: /Users/phs/github/plscope-utils/database/test/package/test_lineage_util.pks... done.
Formatting file 24 of 56: /Users/phs/github/plscope-utils/database/test/package/test_parse_util.pkb... done.
Formatting file 25 of 56: /Users/phs/github/plscope-utils/database/test/package/test_parse_util.pks... done.
Formatting file 26 of 56: /Users/phs/github/plscope-utils/database/test/package/test_plscope_context.pkb... done.
Formatting file 27 of 56: /Users/phs/github/plscope-utils/database/test/package/test_plscope_context.pks... done.
Formatting file 28 of 56: /Users/phs/github/plscope-utils/database/test/package/test_plscope_identifiers.pkb... done.
Formatting file 29 of 56: /Users/phs/github/plscope-utils/database/test/package/test_plscope_identifiers.pks... done.
Formatting file 30 of 56: /Users/phs/github/plscope-utils/database/test/package/test_type_util.pkb... done.
Formatting file 31 of 56: /Users/phs/github/plscope-utils/database/test/package/test_type_util.pks... done.
Formatting file 32 of 56: /Users/phs/github/plscope-utils/database/utils/context/plscope.ctx... done.
Formatting file 33 of 56: /Users/phs/github/plscope-utils/database/utils/package/dd_util.pkb... done.
Formatting file 34 of 56: /Users/phs/github/plscope-utils/database/utils/package/dd_util.pks... done.
Formatting file 35 of 56: /Users/phs/github/plscope-utils/database/utils/package/lineage_util.pkb... done.
Formatting file 36 of 56: /Users/phs/github/plscope-utils/database/utils/package/lineage_util.pks... done.
Formatting file 37 of 56: /Users/phs/github/plscope-utils/database/utils/package/parse_util.pkb... done.
Formatting file 38 of 56: /Users/phs/github/plscope-utils/database/utils/package/parse_util.pks... done.
Formatting file 39 of 56: /Users/phs/github/plscope-utils/database/utils/package/plscope_context.pkb... done.
Formatting file 40 of 56: /Users/phs/github/plscope-utils/database/utils/package/plscope_context.pks... done.
Formatting file 41 of 56: /Users/phs/github/plscope-utils/database/utils/package/type_util.pkb... done.
Formatting file 42 of 56: /Users/phs/github/plscope-utils/database/utils/package/type_util.pks... done.
Formatting file 43 of 56: /Users/phs/github/plscope-utils/database/utils/type/col_lineage_type.sql... done.
Formatting file 44 of 56: /Users/phs/github/plscope-utils/database/utils/type/col_type.sql... done.
Formatting file 45 of 56: /Users/phs/github/plscope-utils/database/utils/type/obj_type.sql... done.
Formatting file 46 of 56: /Users/phs/github/plscope-utils/database/utils/type/t_col_lineage_type.sql... done.
Formatting file 47 of 56: /Users/phs/github/plscope-utils/database/utils/type/t_col_type.sql... done.
Formatting file 48 of 56: /Users/phs/github/plscope-utils/database/utils/type/t_obj_type.sql... done.
Formatting file 49 of 56: /Users/phs/github/plscope-utils/database/utils/user/plscope.sql... done.
Formatting file 50 of 56: /Users/phs/github/plscope-utils/database/utils/view/plscope_col_usage.sql... done.
Formatting file 51 of 56: /Users/phs/github/plscope-utils/database/utils/view/plscope_identifiers.sql... done.
Formatting file 52 of 56: /Users/phs/github/plscope-utils/database/utils/view/plscope_ins_lineage.sql... done.
Formatting file 53 of 56: /Users/phs/github/plscope-utils/database/utils/view/plscope_naming.sql... done.
Formatting file 54 of 56: /Users/phs/github/plscope-utils/database/utils/view/plscope_statements.sql... done.
Formatting file 55 of 56: /Users/phs/github/plscope-utils/database/utils/view/plscope_tab_usage.sql... done.
Formatting file 56 of 56: /Users/phs/github/plscope-utils/sqldev/README.md... done.

Finally, a working native image.

Testing

I have a total of 604 JUnit tests for the formatter. When all tests succeed I can expect that the executable JAR works as well.

Now we need additional tests to detect runtime errors that happen only when running a native image. This is necessary for missing configurations as shown above, but also for other cases where the native image behaves differently.

In my case, I want to run three existing JUnit tests for the main method in TvdFormat. How do I do that? A good choice is to use the Maven plugin for GraalVM Native Image building (of course there is also a plugin for Gradle). This plugin produces a dedicated native image for tests. When you run this native image all configured tests are executed and the results are shown on the console. A non-zero exit status indicates a failure.

When you run mvn -Dnative.skip=false integration-test a native image named native-tests is created and executed during the build. You can run it also after the build via ./native-tests. It produces the following console output:

Console output of the native-tests
JUnit Platform on Native Image - report
----------------------------------------

com.trivadis.plsql.formatter.standalone.tests.TvdFormatTest > jsonArrayFileTest() SUCCESSFUL

com.trivadis.plsql.formatter.standalone.tests.TvdFormatTest > jsonArrayDirTest() SUCCESSFUL

com.trivadis.plsql.formatter.standalone.tests.TvdFormatTest > jsonObjectFileTest() SUCCESSFUL


Test run finished after 3198 ms
[         2 containers found      ]
[         0 containers skipped    ]
[         2 containers started    ]
[         0 containers aborted    ]
[         2 containers successful ]
[         0 containers failed     ]
[         3 tests found           ]
[         0 tests skipped         ]
[         3 tests started         ]
[         0 tests aborted         ]
[         3 tests successful      ]
[         0 tests failed          ]

You find these JUnit tests here and the Maven configuration here.

Maybe you wonder why I do not run all JUnit tests. Simply put, they do not work. Some of them intentionally, because they are testing the integration with SQLcl which is not applicable in the standalone image (the necessary libraries are not included by purpose). Other test cases require configuration changes or need to be rewritten for use in native images. In other words, there is still work to be done.

Performance

What to Measure

Let’s compare the runtimes of the following execution variants:

  1. Native image with GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05
  2. Executable JAR with GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05)
  3. Executable JAR with JDK 17.0.1 for macOS ARM 64

The GraalVM JDK is not yet available for macOS ARM 64 (see GitHub issue). This means that we must use the Intel x64 variant, which requires Rosetta 2 to translate the Intel x64 instructions for the M1 Max chip. This emulation works quite well and is transparent for the user. However, it needs time. To get an idea of the performance improvement of the native image technology, option (1) and (2) should be compared. Option (3) is still interesting for comparison with option (2). It shows the impact of the Rosetta 2 chip emulation.

I’d like to measure the performance of these two scenarios:

  1. Startup time (start the executable without parameters to show the help)
  2. Formatting 56 files (of plscope-util project, that is what we’ve run previously)

Scenario 1 – Startup Time

In this scenario, we measure the startup time of the formatter. This means we call the formatter without parameters to show the help. The shell script shows what I measured.


Shell script - startup time

bench1.sh
#!/bin/zsh
 
call() {
    echo "$1" "($2)"
    for ((i=0; i<3; i++))
    do
       eval "time $2 > /dev/null"
    done;
}
 
echo
echo Scenario 1 - Startup Time
echo -------------------------
unset JAVA_HOME
call "1) Native Image" "./tvdformat"
export JAVA_HOME=$HOME/Applications/graalvm-ce-java17-21.3.0/Contents/Home
call "2) GraalVM" "java -jar ./tvdformat.jar"
export JAVA_HOME=`/usr/libexec/java_home -v 17`
call "3) ARM JDK" "java -jar ./tvdformat.jar"
Console output of shell script
Scenario 1 - Startup Time
-------------------------
1) Native Image (./tvdformat)
  0.29s user 0.05s system 94% cpu 0.355 total
  0.29s user 0.04s system 96% cpu 0.336 total
  0.29s user 0.04s system 96% cpu 0.337 total
2) GraalVM (java -jar ./tvdformat.jar)
  2.99s user 0.43s system 143% cpu 2.387 total
  2.97s user 0.42s system 142% cpu 2.376 total
  2.99s user 0.43s system 142% cpu 2.392 total
3) ARM JDK (java -jar ./tvdformat.jar)
  0.65s user 0.04s system 167% cpu 0.414 total
  0.65s user 0.05s system 165% cpu 0.419 total
  0.64s user 0.04s system 159% cpu 0.427 total


I used the result of the second execution per variant to create the chart.

Scenario 1 - Startup Time - Native Image vs Executable JAR

The native image delivers by far the fastest startup times. It’s 7 times faster and uses 10 times less CPU resources. Rosetta 2 leads to an overhead of about factor 4.7 from a CPU usage perspective.

Scenario 2 – Formatting 56 files

In this scenario, we measure the time to format 56 PL/SQL and SQL files of the plscope-util project. The shell script shows what I measured.


Shell script - formatting 56 files

bench2.sh
#!/bin/zsh
 
call() {
    echo "$1" "($2)"
    for ((i=0; i<3; i++))
    do
       eval "time $2 $HOME/github/plscope-utils \
            xml=$HOME/github/trivadis/plsql-formatter-settings/settings/sql_developer/trivadis_advanced_format.xml \
            arbori=$HOME/github/trivadis/plsql-formatter-settings/settings/sql_developer/trivadis_custom_format.arbori \
            > /dev/null"
    done;
}
 
echo
echo Scenario 2 - Formatting 56 files
echo --------------------------------
unset JAVA_HOME
call "1) Native Image" "./tvdformat"
export JAVA_HOME=$HOME/Applications/graalvm-ce-java17-21.3.0/Contents/Home
call "2) GraalVM" "java -jar ./tvdformat.jar"
export JAVA_HOME=`/usr/libexec/java_home -v 17`
call "3) ARM JDK" "java -jar ./tvdformat.jar"
Console output of shell script
Scenario 2 - Formatting 56 files
--------------------------------
1) Native Image (./tvdformat)
  44.91s user 1.82s system 305% cpu 15.317 total
  45.12s user 1.69s system 305% cpu 15.304 total
  45.85s user 1.61s system 309% cpu 15.344 total
2) GraalVM (java -jar ./tvdformat.jar)
  77.32s user 4.22s system 365% cpu 22.297 total
  77.59s user 4.32s system 371% cpu 22.044 total
  82.49s user 4.77s system 372% cpu 23.451 total
3) ARM JDK (java -jar ./tvdformat.jar)
  17.47s user 0.62s system 244% cpu 7.385 total
  17.02s user 0.57s system 242% cpu 7.262 total
  17.84s user 0.66s system 246% cpu 7.495 total


I used the result of the second execution per variant to create the chart.

Scenario 2 - Formatting 56 Files - Native Image vs Executable JAR

The native image is about 30% faster and uses about 40% less CPU resources. Rosetta 2 leads to an overhead of about factor 4.7 from a CPU usage perspective.

Conclusion

Wow, just wow, when I look at the performance and resource consumption figures of a native image. Startup times are really amazing and I was surprised by the results when formatting 56 files. The native image only consumes about half of the CPU resources compared to the executable JAR variant. This means lower operating costs while improving the end-user experience.

The price for a native image is higher development costs and the risk of runtime errors that would not occur in traditional Java environments. You definitely need to adjust your testing strategies to mitigate that risk. This is mandatory, not an option.

In this blog post, I eliminated all known runtime errors after four builds. In reality, it took much longer. I spent a lot of time hunting down the reasons for different behaviours between the executable JAR and the native image. This might improve once the debugger becomes available for the community edition. But even then you have to build a new image after a change. Only then you can start debugging. This results in long feedback loops. It does not matter whether you instrument your code or use a debugger.

This technology is fairly new. I’m sure that the tooling will improve over time. In the meantime, I’d prefer to use native images only for simple artefacts.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.