lucene/gradle/generation/javacc.gradle

341 lines
12 KiB
Groovy

import java.nio.charset.Charset
import java.util.function.Function
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This adds javacc generation support.
configure(rootProject) {
configurations {
javacc
}
dependencies {
javacc "net.java.dev.javacc:javacc:${scriptDepVersions['javacc']}"
}
task javacc() {
description "Regenerate sources for corresponding javacc grammar files."
group "generation"
dependsOn allprojects.collect { prj -> prj.tasks.withType(JavaCCTask) }
}
}
def commonCleanups = { FileTree generatedFiles ->
// This is a minor typo in a comment that nonetheless people have hand-corrected in the past.
generatedFiles.matching({ include "CharStream.java" }).each {file ->
modifyFile(file, { text ->
return text.replace(
"implemetation",
"implementation");
})
}
generatedFiles.each {file ->
modifyFile(file, { text ->
// Normalize EOLs and tabs (EOLs are a side-effect of modifyFile).
text = text.replace("\t", " ");
text = text.replaceAll("JavaCC - OriginalChecksum=[^*]+", "(filtered)")
text = text.replace("StringBuffer", "StringBuilder")
return text
})
}
generatedFiles.matching({ include "*TokenManager.java" }).each { file ->
modifyFile(file, { text ->
// Remove redundant imports.
text = text.replaceAll(
/(?m)^import .+/,
"")
// Add CharStream imports.
text = text.replaceAll(
/package (.+)/,
'''
package $1
import org.apache.lucene.queryparser.charstream.CharStream;
'''.trim())
// Eliminates redundant cast message.
text = text.replace(
"int hiByte = (int)(curChar >> 8);",
"int hiByte = curChar >> 8;")
// Access to forbidden APIs.
text = text.replace(
"public java.io.PrintStream debugStream = System.out;",
"// (debugStream omitted).")
text = text.replace(
"public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }",
"// (setDebugStream omitted).")
text = text.replace(
"public class QueryParserTokenManager ",
'@SuppressWarnings("unused") public class QueryParserTokenManager ')
text = text.replace(
"public class StandardSyntaxParserTokenManager ",
'@SuppressWarnings("unused") public class StandardSyntaxParserTokenManager ')
return text
})
}
}
configure(project(":lucene:queryparser")) {
task javaccParserClassicInternal(type: JavaCCTask) {
description "Regenerate classic query parser from lucene/queryparser/classic/QueryParser.jj"
group "generation"
javaccFile = file('src/java/org/apache/lucene/queryparser/classic/QueryParser.jj')
afterGenerate << commonCleanups
afterGenerate << { FileTree generatedFiles ->
generatedFiles.matching { include "QueryParser.java" }.each { file ->
modifyFile(file, { text ->
text = text.replace(
"public QueryParser(CharStream ",
"protected QueryParser(CharStream ")
text = text.replace(
"public QueryParser(QueryParserTokenManager ",
"protected QueryParser(QueryParserTokenManager ")
text = text.replace(
"new java.util.ArrayList<int[]>",
"new java.util.ArrayList<>")
text = text.replace(
"final private LookaheadSuccess jj_ls =",
"static final private LookaheadSuccess jj_ls =")
text = text.replace(
"public class QueryParser ",
'@SuppressWarnings({"unused","null"}) public class QueryParser ')
text = text.replace(
"final public Query TopLevelQuery(",
"@Override final public Query TopLevelQuery(")
text = text.replace(
"public void ReInit(CharStream ",
"@Override public void ReInit(CharStream ")
return text
})
}
}
}
task javaccParserSurroundInternal(type: JavaCCTask) {
description "Regenerate surround query parser from lucene/queryparser/surround/parser/QueryParser.jj"
group "generation"
javaccFile = file('src/java/org/apache/lucene/queryparser/surround/parser/QueryParser.jj')
afterGenerate << commonCleanups
afterGenerate << { FileTree generatedFiles ->
generatedFiles.matching { include "QueryParser.java" }.each { file ->
modifyFile(file, { text ->
text = text.replace(
"import org.apache.lucene.analysis.TokenStream;",
"")
text = text.replace(
"new java.util.ArrayList<int[]>",
"new java.util.ArrayList<>")
text = text.replace(
"public class QueryParser ",
'@SuppressWarnings({"unused","null"}) public class QueryParser ')
return text
})
}
}
}
task javaccParserFlexibleInternal(type: JavaCCTask) {
description "Regenerate flexible query parser from queryparser/flexible/standard/parser/StandardSyntaxParser.jj"
group "generation"
javaccFile = file('src/java/org/apache/lucene/queryparser/flexible/standard/parser/StandardSyntaxParser.jj')
afterGenerate << commonCleanups
afterGenerate << { FileTree generatedFiles ->
generatedFiles.matching { include "ParseException.java" }.each { file ->
modifyFile(file, { text ->
// Modify constructor.
text = text.replace(
"class ParseException extends Exception",
"class ParseException extends QueryNodeParseException")
// Modify imports.
text = text.replace(
"package org.apache.lucene.queryparser.flexible.standard.parser;", '''\
package org.apache.lucene.queryparser.flexible.standard.parser;
import org.apache.lucene.queryparser.flexible.messages.*;
import org.apache.lucene.queryparser.flexible.core.*;
import org.apache.lucene.queryparser.flexible.core.messages.*;
''')
// Modify constructors and code bits
text = text.replaceAll(
/(?s)[ ]*public ParseException\(Token currentTokenVal[^}]+[}]/, '''\
public ParseException(Token currentTokenVal,
int[][] expectedTokenSequencesVal, String[] tokenImageVal)
{
super(new MessageImpl(QueryParserMessages.INVALID_SYNTAX, initialise(
currentTokenVal, expectedTokenSequencesVal, tokenImageVal)));
this.currentToken = currentTokenVal;
this.expectedTokenSequences = expectedTokenSequencesVal;
this.tokenImage = tokenImageVal;
}
''')
text = text.replaceAll(
/(?s)[ ]*public ParseException\(String message\)[^}]+[}]/, '''\
public ParseException(Message message)
{
super(message);
}
''')
text = text.replaceAll(
/(?s)[ ]*public ParseException\(\)[^}]+[}]/, '''\
public ParseException()
{
super(new MessageImpl(QueryParserMessages.INVALID_SYNTAX, "Error"));
}
''')
return text
})
}
generatedFiles.matching { include "StandardSyntaxParser.java" }.each { file ->
modifyFile(file, { text ->
// Remove redundant cast
text = text.replace(
"new java.util.ArrayList<int[]>",
"new java.util.ArrayList<>")
text = text.replace(
"new ArrayList<QueryNode>()",
"new ArrayList<>()")
text = text.replace(
"Collections.<QueryNode> singletonList",
"Collections.singletonList")
text = text.replace(
"public class StandardSyntaxParser ",
'@SuppressWarnings({"unused","null"}) public class StandardSyntaxParser ')
return text
})
}
}
}
task javacc() {
description "Regenerate query parsers (javacc syntax definitions)."
group "generation"
dependsOn wrapWithPersistentChecksums(javaccParserClassicInternal, [ andThenTasks: ["spotlessJava", "spotlessJavaApply"] ]),
wrapWithPersistentChecksums(javaccParserSurroundInternal, [ andThenTasks: ["spotlessJava", "spotlessJavaApply"] ]),
wrapWithPersistentChecksums(javaccParserFlexibleInternal, [ andThenTasks: ["spotlessJava", "spotlessJavaApply"] ])
}
regenerate.dependsOn javacc
}
// We always regenerate, no need to declare outputs.
class JavaCCTask extends DefaultTask {
@InputFile
File javaccFile
/**
* Apply closures to all generated files before they're copied back
* to mainline code.
*/
// A subtle bug here is that this makes it not an input... should be a list of replacements instead?
@Internal
List<Closure<FileTree>> afterGenerate = new ArrayList<>()
@OutputFiles
List<File> getGeneratedSources() {
// Return the list of generated files.
def baseDir = javaccFile.parentFile
def baseName = javaccFile.name.replace(".jj", "")
return [
project.file("${baseDir}/${baseName}.java"),
project.file("${baseDir}/${baseName}Constants.java"),
project.file("${baseDir}/${baseName}TokenManager.java"),
project.file("${baseDir}/ParseException.java"),
project.file("${baseDir}/Token.java"),
project.file("${baseDir}/TokenMgrError.java")
]
}
JavaCCTask() {
dependsOn(project.rootProject.configurations.javacc)
}
@TaskAction
def generate() {
if (!javaccFile || !javaccFile.exists()) {
throw new GradleException("Input file does not exist: ${javaccFile}")
}
// Run javacc generation into temporary folder so that we know all the generated files
// and can post-process them easily.
def tempDir = this.getTemporaryDir()
tempDir.mkdirs()
project.delete project.fileTree(tempDir, { include: "**/*.java" })
def targetDir = javaccFile.parentFile
logger.lifecycle("Recompiling JavaCC: ${project.rootDir.relativePath(javaccFile)}")
def output = new ByteArrayOutputStream()
def result = project.javaexec {
classpath {
project.rootProject.configurations.javacc
}
ignoreExitValue = true
standardOutput = output
errorOutput = output
main = "org.javacc.parser.Main"
args += [
"-OUTPUT_DIRECTORY=${tempDir}",
javaccFile
]
}
// Unless we request verbose logging, don't emit javacc output.
if (result.exitValue != 0) {
throw new GradleException("JavaCC failed to compile ${javaccFile}, here is the compilation output:\n${output}")
}
// Make sure we don't have warnings.
if (output.toString(Charset.defaultCharset()).contains("Warning:")) {
throw new GradleException("JavaCC emitted warnings for ${javaccFile}, here is the compilation output:\n${output}")
}
// Apply any custom modifications.
def generatedFiles = project.fileTree(tempDir)
afterGenerate.each {closure ->
closure.call(generatedFiles)
}
// Copy back to mainline sources.
project.copy {
from tempDir
into targetDir
// We don't need CharStream interface as we redirect to our own.
exclude "CharStream.java"
}
}
}