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", "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", "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", "new java.util.ArrayList<>") text = text.replace( "new ArrayList()", "new ArrayList<>()") text = text.replace( "Collections. 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> afterGenerate = new ArrayList<>() @OutputFiles List 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" } } }