Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype a query by name method parser #523

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
125 changes: 125 additions & 0 deletions tools/pom.xml
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2024 Contributors to the Eclipse Foundation
~
~ Licensed 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.
~
~ SPDX-License-Identifier: Apache-2.0
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>


<groupId>jakarta.data</groupId>
<artifactId>jakarta.data-tools</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>Jakarta Data Tools</name>

<properties>
<antlr.version>4.13.1</antlr.version>
<arquillian.version>1.8.0.Final</arquillian.version>
<junit.version>5.10.2</junit.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>${arquillian.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>

<dependency>
<groupId>jakarta.data</groupId>
<artifactId>jakarta.data-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Arquillian -->
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-container-test-spi</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-container-test-api</artifactId>
</dependency>
<!-- ANTLR -->
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4</artifactId>
<version>${antlr.version}</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr.version}</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
<version>4.3.4</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr.version}</version>
<configuration>
<listener>true</listener>
<visitor>true</visitor>
<outputDirectory>${project.build.directory}/generated-sources/ee/jakarta/tck/data/tools/antlr</outputDirectory>
</configuration>
<executions>
<execution>
<id>antlr</id>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>

</plugins>
</build>
</project>
98 changes: 98 additions & 0 deletions tools/src/main/antlr4/QBN.g4
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
// 4.6.1. BNF Grammar for Query Methods
grammar QBN;

@header {
// TBD
package ee.jakarta.tck.data.tools.antlr;
}

query_method : find_query | action_query ;

find_query : find limit? ignored_text? restriction? order? ;
action_query : action ignored_text? restriction? ;

action : delete | update | count | exists ;

find : 'find' ;
delete : 'delete' ;
update : 'update' ;
count : 'count' ;
exists : 'exists' ;

restriction : BY predicate ;

limit : FIRST INTEGER? ;

predicate : condition ( (AND | OR) condition )* ;

condition : property ignore_case? not? operator? ;
ignore_case : IGNORE_CASE ;
not : NOT ;

operator
: CONTAINS
| ENDSWITH
| STARTSWITH
| LESSTHAN
| LESSTHANEQUAL
| GREATERTHAN
| GREATERTHANEQUAL
| BETWEEN
| EMPTY
| LIKE
| IN
| NULL
| TRUE
| FALSE
;
property : (IDENTIFIER | IDENTIFIER '_' property)+ ;

order : ORDER_BY ( property | order_item+) ;

order_item : property ( ASC | DESC ) ;

ignored_text : IDENTIFIER ;

// Lexer rules
FIRST : 'First' ;
BY : 'By' ;
CONTAINS : 'Contains' ;
ENDSWITH : 'EndsWith' ;
STARTSWITH : 'StartsWith' ;
LESSTHAN : 'LessThan' ;
LESSTHANEQUAL : 'LessThanEqual' ;
GREATERTHAN : 'GreaterThan' ;
GREATERTHANEQUAL : 'GreaterThanEqual' ;
BETWEEN : 'Between' ;
EMPTY : 'Empty' ;
LIKE : 'Like' ;
IN : 'In' ;
NULL : 'Null' ;
TRUE : 'True' ;
FALSE : 'False' ;
IGNORE_CASE : 'IgnoreCase' ;
NOT : 'Not' ;
ORDER_BY : 'OrderBy' ;
AND : 'And' ;
OR : 'Or' ;
ASC : 'Asc' ;
DESC : 'Desc' ;

IDENTIFIER : ([A-Z][a-z]+)+? ;
INTEGER : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
159 changes: 159 additions & 0 deletions tools/src/main/java/ee/jakarta/tck/data/tools/annp/AnnProcUtils.java
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* Licensed 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
*
* SPDX-License-Identifier: Apache-2.0
*/
package ee.jakarta.tck.data.tools.annp;

import ee.jakarta.tck.data.tools.qbyn.ParseUtils;
import ee.jakarta.tck.data.tools.qbyn.QueryByNameInfo;
import jakarta.data.repository.Delete;
import jakarta.data.repository.Find;
import jakarta.data.repository.Insert;
import jakarta.data.repository.Query;
import jakarta.data.repository.Save;
import jakarta.data.repository.Update;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.STGroupFile;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class AnnProcUtils {
// The name of the template for the TCK override imports
public static final String TCK_IMPORTS = "/tckImports";
// The name of the template for the TCK overrides
public static final String TCK_OVERRIDES = "/tckOverrides";

/**
* Get a list of non-lifecycle methods in a type element. This will also process superinterfaces
* @param typeElement a repository interface
* @return a list of non-lifecycle methods as candidate repository methods
*/
public static List<ExecutableElement> methodsIn(TypeElement typeElement) {
ArrayList<ExecutableElement> methods = new ArrayList<>();
List<ExecutableElement> typeMethods = methodsIn(typeElement.getEnclosedElements());
methods.addAll(typeMethods);
List<? extends TypeMirror> superifaces = typeElement.getInterfaces();
for (TypeMirror iface : superifaces) {
if(iface instanceof DeclaredType) {
DeclaredType dt = (DeclaredType) iface;
System.out.printf("Processing superinterface %s<%s>\n", dt.asElement(), dt.getTypeArguments());
methods.addAll(methodsIn((TypeElement) dt.asElement()));
}
}
return methods;
}

/**
* Get a list of non-lifecycle methods in a list of repository elements
* @param elements - a list of repository elements
* @return possibly empty list of non-lifecycle methods
*/
public static List<ExecutableElement> methodsIn(Iterable<? extends Element> elements) {
ArrayList<ExecutableElement> methods = new ArrayList<>();
for (Element e : elements) {
if(e.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) e;
// Skip lifecycle methods
if(!isLifeCycleMethod(method)) {
methods.add(method);
}
}
}
return methods;
}

/**
* Is a method annotated with a lifecycle or Query annotation
* @param method a repository method
* @return true if the method is a lifecycle method
*/
public static boolean isLifeCycleMethod(ExecutableElement method) {
boolean standardLifecycle = method.getAnnotation(Insert.class) != null
|| method.getAnnotation(Find.class) != null
|| method.getAnnotation(Update.class) != null
|| method.getAnnotation(Save.class) != null
|| method.getAnnotation(Delete.class) != null
|| method.getAnnotation(Query.class) != null;
return standardLifecycle;
}

public static String getFullyQualifiedName(Element element) {
if (element instanceof TypeElement) {
return ((TypeElement) element).getQualifiedName().toString();
}
return null;
}


public static QueryByNameInfo isQBN(ExecutableElement m) {
String methodName = m.getSimpleName().toString();
try {
return ParseUtils.parseQueryByName(methodName);
}
catch (Throwable e) {
System.out.printf("Failed to parse %s: %s\n", methodName, e.getMessage());
}
return null;
}

/**
* Write a repository interface to a source file using the {@linkplain RepositoryInfo}. This uses the
* RepoTemplate.stg template file to generate the source code. It also looks for a
*
* @param repo - parsed repository info
* @param processingEnv - the processing environment
* @throws IOException - if the file cannot be written
*/
public static void writeRepositoryInterface(RepositoryInfo repo, ProcessingEnvironment processingEnv) throws IOException {
STGroup repoGroup = new STGroupFile("RepoTemplate.stg");
ST genRepo = repoGroup.getInstanceOf("genRepo");
try {
URL stgURL = AnnProcUtils.class.getResource("/"+repo.getFqn()+".stg");
STGroup tckGroup = new STGroupFile(stgURL);
long count = tckGroup.getTemplateNames().stream().filter(t -> t.equals(TCK_IMPORTS) | t.equals(TCK_OVERRIDES)).count();
if(count != 2) {
System.out.printf("No TCK overrides for %s\n", repo.getFqn());
} else {
tckGroup.importTemplates(repoGroup);
System.out.printf("Found TCK overrides(%s) for %s\n", tckGroup.getRootDirURL(), repo.getFqn());
System.out.printf("tckGroup: %s\n", tckGroup.show());
genRepo = tckGroup.getInstanceOf("genRepo");
}
} catch (IllegalArgumentException e) {
System.out.printf("No TCK overrides for %s\n", repo.getFqn());
}

genRepo.add("repo", repo);

String ifaceSrc = genRepo.render();
String ifaceName = repo.getFqn() + "$";
Filer filer = processingEnv.getFiler();
JavaFileObject srcFile = filer.createSourceFile(ifaceName, repo.getRepositoryElement());
try(Writer writer = srcFile.openWriter()) {
writer.write(ifaceSrc);
writer.flush();
}
System.out.printf("Wrote %s, to: %s\n", ifaceName, srcFile.toUri());
}
}