Skip to content

Commit cf5bbc9

Browse files
hayssamsclaude
andauthored
fix: split CCJSqlParserTokenManager static initializers to avoid 64KB method limit (#2425)
The generated CCJSqlParserTokenManager class has a clinit method that reaches 64,452 bytes -- dangerously close to the JVM 65,535 byte limit. ASM-based tools like sbt-assembly add transformation overhead that pushes it over, causing MethodTooLargeException during fat JAR creation. Add a splitTokenManagerStaticInit Gradle task that post-processes the generated Java file after JavaCC code generation, extracting 6 large static array initializations (stringLiterals, jjstrLiteralImages, jjmatchKinds, jjnewLexState, jjcompositeState, jjnextStateSet) into separate private static methods. This reduces clinit from 64,452 bytes to 404 bytes. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cae3e0d commit cf5bbc9

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

build.gradle

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,79 @@ compileJavacc {
198198
]
199199
}
200200

201+
// Post-process the generated CCJSqlParserTokenManager.java to split large static
202+
// array initializers into separate methods, preventing the <clinit> method from
203+
// exceeding the JVM's 64KB bytecode limit (which breaks ASM-based tools like sbt-assembly).
204+
tasks.register('splitTokenManagerStaticInit') {
205+
dependsOn(compileJavacc)
206+
207+
def tokenManagerFile = layout.buildDirectory.file(
208+
"generated/javacc/net/sf/jsqlparser/parser/CCJSqlParserTokenManager.java"
209+
)
210+
211+
inputs.file(tokenManagerFile)
212+
outputs.file(tokenManagerFile)
213+
214+
doLast {
215+
def file = tokenManagerFile.get().asFile
216+
if (!file.exists()) {
217+
throw new GradleException("CCJSqlParserTokenManager.java not found at ${file}")
218+
}
219+
def content = file.text
220+
221+
// Pattern matches static final array field declarations with inline initialization.
222+
// We extract large ones and move their initialization into separate methods.
223+
def fieldsToExtract = [
224+
// [regex-safe field name, array type for method return]
225+
['stringLiterals', 'int[]'],
226+
['jjstrLiteralImages', 'String[]'],
227+
['jjmatchKinds', 'int[]'],
228+
['jjnewLexState', 'int[]'],
229+
]
230+
231+
fieldsToExtract.each { entry ->
232+
def fieldName = entry[0]
233+
def arrayType = entry[1]
234+
235+
// Match: <modifiers> <type> <fieldName> = { ... };
236+
// The field declaration may use 'public' or 'private' and 'static final'
237+
def pattern = ~"(?s)((?:public|private)\\s+static\\s+final\\s+${java.util.regex.Pattern.quote(arrayType)}\\s+${fieldName}\\s*=\\s*)\\{(.*?)\\};"
238+
def matcher = pattern.matcher(content)
239+
if (matcher.find()) {
240+
def prefix = matcher.group(1)
241+
def body = matcher.group(2)
242+
def methodName = "_init_${fieldName}"
243+
def replacement = "${prefix}${methodName}();\n" +
244+
" private static ${arrayType} ${methodName}() { return new ${arrayType} {${body}}; }"
245+
content = matcher.replaceFirst(java.util.regex.Matcher.quoteReplacement(replacement))
246+
logger.lifecycle("splitTokenManagerStaticInit: extracted ${fieldName} initialization into ${methodName}()")
247+
}
248+
}
249+
250+
// Handle int[][] arrays separately (jjcompositeState, jjnextStateSet)
251+
def arrayArrayFields = ['jjcompositeState', 'jjnextStateSet']
252+
arrayArrayFields.each { fieldName ->
253+
def pattern = ~"(?s)(private\\s+static\\s+final\\s+int\\[\\]\\[\\]\\s+${fieldName}\\s*=\\s*)\\{(.*?)\\};"
254+
def matcher = pattern.matcher(content)
255+
if (matcher.find()) {
256+
def prefix = matcher.group(1)
257+
def body = matcher.group(2)
258+
def methodName = "_init_${fieldName}"
259+
def replacement = "${prefix}${methodName}();\n" +
260+
" private static int[][] ${methodName}() { return new int[][] {${body}}; }"
261+
content = matcher.replaceFirst(java.util.regex.Matcher.quoteReplacement(replacement))
262+
logger.lifecycle("splitTokenManagerStaticInit: extracted ${fieldName} initialization into ${methodName}()")
263+
}
264+
}
265+
266+
file.text = content
267+
}
268+
}
269+
270+
tasks.withType(JavaCompile).configureEach {
271+
dependsOn('splitTokenManagerStaticInit')
272+
}
273+
201274
java {
202275
withSourcesJar()
203276
withJavadocJar()

0 commit comments

Comments
 (0)