Skip to content

Commit

Permalink
feat: Qute: add support for template records
Browse files Browse the repository at this point in the history
Fixes redhat-developer#956

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jul 10, 2024
1 parent 48ec48e commit 426e102
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 6 deletions.
3 changes: 2 additions & 1 deletion qute.jdt/com.redhat.qute.jdt/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@

<!-- Data model providers for Qute core -->
<extension point="com.redhat.qute.jdt.dataModelProviders">
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateFieldSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.CheckedTemplateSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateRecordsSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateExtensionAnnotationSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateFieldSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateDataAnnotationSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateEnumAnnotationSupport" />
<provider class="com.redhat.qute.jdt.internal.template.datamodel.TemplateGlobalAnnotationSupport" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class QuteJavaConstants {

public static final String TEMPLATE_CLASS = "io.quarkus.qute.Template";

public static final String TEMPLATE_INSTANCE_INTERFACE = "io.quarkus.qute.TemplateInstance";

public static final String ENGINE_BUILDER_CLASS = "io.quarkus.qute.EngineBuilder";

public static final String VALUE_ANNOTATION_NAME = "value";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.StringLiteral;
Expand All @@ -55,6 +57,7 @@
* <ul>
* <li>declared methods which have class annotated with @CheckedTemplate.</li>
* <li>declared field which have Template as type.</li>
* <li>declared record which implements TemplateInstance.</li>
* </ul>
*
* @author Angelo ZERR
Expand Down Expand Up @@ -92,6 +95,17 @@ public boolean visit(CompilationUnit node) {
return super.visit(node);
}

/**
* Support for "Template Fields"
*
* <p>
* private Template items;
* </p>
*
* @see <a href=
* "https://quarkus.io/guides/qute-reference#quarkus_integration">Quarkus
* Integration</a>
*/
@Override
public boolean visit(FieldDeclaration node) {
Type type = node.getType();
Expand Down Expand Up @@ -141,6 +155,19 @@ private AnnotationLocationSupport getAnnotationLocationSupport() {
return annotationLocationSupport;
}

/**
* Support for "TypeSafe Templates"
*
* <p>
*
* @CheckedTemplate public static class Templates { public static native
* TemplateInstance book(Book book);
* </p>
*
* @see <a href=
* "https://quarkus.io/guides/qute-reference#typesafe_templates">TypeSafe
* Templates</a>
*/
@SuppressWarnings("rawtypes")
@Override
public boolean visit(TypeDeclaration node) {
Expand All @@ -167,6 +194,20 @@ public boolean visit(TypeDeclaration node) {
return super.visit(node);
}

/**
* Support for "Template Records"
*
* @ses <a href=
* "https://quarkus.io/guides/qute-reference#template-records">Template
* Records</a>
*/
@Override
public boolean visit(RecordDeclaration node) {
String recordName = node.getName().getIdentifier();
collectTemplateLink(node, null, node, null, recordName, false);
return super.visit(node);
}

/**
* Returns true if @CheckedTemplate annotation declares that fragment must be
* ignored and false otherwise.
Expand Down Expand Up @@ -217,7 +258,7 @@ private void collectTemplateLink(MethodDeclaration methodDeclaration, TypeDeclar
collectTemplateLink(methodDeclaration, null, type, className, methodName, ignoreFragment);
}

private void collectTemplateLink(ASTNode fieldOrMethod, StringLiteral locationAnnotation, TypeDeclaration type,
private void collectTemplateLink(ASTNode fieldOrMethod, StringLiteral locationAnnotation, AbstractTypeDeclaration type,
String className, String fieldOrMethodName, boolean ignoreFragment) {
try {
String location = locationAnnotation != null ? locationAnnotation.getLiteralValue() : null;
Expand Down Expand Up @@ -256,12 +297,17 @@ protected Range createRange(ASTNode fieldOrMethod) throws JavaModelException {
SimpleName methodName = method.getName();
return utils.toRange(typeRoot, methodName.getStartPosition(), methodName.getLength());
}
case ASTNode.RECORD_DECLARATION: {
RecordDeclaration recordDecl = (RecordDeclaration) fieldOrMethod;
SimpleName recordName = recordDecl.getName();
return utils.toRange(typeRoot, recordName.getStartPosition(), recordName.getLength());
}
default:
return utils.toRange(typeRoot, fieldOrMethod.getStartPosition(), fieldOrMethod.getLength());
}
}

protected abstract void collectTemplateLink(ASTNode node, ASTNode locationAnnotation, TypeDeclaration type,
protected abstract void collectTemplateLink(ASTNode node, ASTNode locationAnnotation, AbstractTypeDeclaration type,
String className, String fieldOrMethodName, String location, IFile templateFile,
TemplatePathInfo templatePathInfo) throws JavaModelException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
Expand Down Expand Up @@ -66,7 +67,7 @@ public QuteJavaCodeLensCollector(ITypeRoot typeRoot, List<CodeLens> lenses, IJDT
}

@Override
protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, TypeDeclaration type,
protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type,
String className, String fieldOrMethodName, String location, IFile templateFile,
TemplatePathInfo templatePathInfo) throws JavaModelException {
if (!templatePathInfo.isValid()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Diagnostic;
Expand Down Expand Up @@ -52,7 +53,7 @@ public QuteJavaDiagnosticsCollector(ITypeRoot typeRoot, List<Diagnostic> diagnos
}

@Override
protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, TypeDeclaration type,
protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type,
String className, String fieldOrMethodName, String location, IFile templateFile,
TemplatePathInfo templatePathInfo) throws JavaModelException {
QuteErrorCode error = getQuteErrorCode(templatePathInfo, templateFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.DocumentLink;
import org.eclipse.lsp4j.Range;
Expand Down Expand Up @@ -52,7 +53,7 @@ public QuteJavaDocumentLinkCollector(ITypeRoot typeRoot, List<DocumentLink> link
}

@Override
protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, TypeDeclaration type,
protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type,
String className, String fieldOrMethodName, String location, IFile templateFile,
TemplatePathInfo templatePathInfo) throws JavaModelException {
if (!templatePathInfo.isValid()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package com.redhat.qute.jdt.internal.template.datamodel;

import static com.redhat.qute.jdt.internal.QuteJavaConstants.TEMPLATE_INSTANCE_INTERFACE;
import static com.redhat.qute.jdt.utils.JDTQuteProjectUtils.getTemplatePath;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;

import com.redhat.qute.commons.datamodel.DataModelParameter;
import com.redhat.qute.commons.datamodel.DataModelTemplate;
import com.redhat.qute.jdt.internal.template.TemplateDataSupport;
import com.redhat.qute.jdt.template.datamodel.AbstractTypeWhichImplementsInterfaceDataModelProvider;
import com.redhat.qute.jdt.template.datamodel.SearchContext;

public class TemplateRecordsSupport extends AbstractTypeWhichImplementsInterfaceDataModelProvider {

private static final String[] INTERFACE_NAMES = { TEMPLATE_INSTANCE_INTERFACE };

@Override
protected String[] getInterfaceNames() {
return INTERFACE_NAMES;
}

@Override
protected void processType(IType type, SearchContext context, IProgressMonitor monitor) throws JavaModelException {
if (!type.isRecord()) {
return;
}
collectDataModelTemplateForTemplateRecord(type, context.getDataModelProject().getTemplates(), monitor);
}

private static void collectDataModelTemplateForTemplateRecord(IType type,
List<DataModelTemplate<DataModelParameter>> templates, IProgressMonitor monitor) throws JavaModelException {
DataModelTemplate<DataModelParameter> template = createTemplateDataModel(type, monitor);
templates.add(template);
}

private static DataModelTemplate<DataModelParameter> createTemplateDataModel(IType type, IProgressMonitor monitor) throws JavaModelException {

String recordName = type.getElementName();
// src/main/resources/templates/${recordName}.qute.html
String templateUri = getTemplatePath(null, recordName, true).getTemplateUri();

// Create template data model with:
// - template uri : Qute template file which must be bind with data model.
// - source type : the record class which defines Templates
// -
DataModelTemplate<DataModelParameter> template = new DataModelTemplate<DataModelParameter>();
template.setParameters(new ArrayList<>());
template.setTemplateUri(templateUri);
template.setSourceType(type.getDeclaringType().getFullyQualifiedName());

// Collect data parameters from the record fields
for(IField field : type.getRecordComponents()) {
DataModelParameter parameter = new DataModelParameter();
parameter.setKey(field.getElementName());
parameter.setSourceType(type.getDeclaringType().getFullyQualifiedName());
template.getParameters().add(parameter);
}

// Collect data parameters for the given template
TemplateDataSupport.collectParametersFromDataMethodInvocation(type, template, monitor);
return template;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.redhat.qute.jdt.template.datamodel;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;

import com.redhat.qute.jdt.internal.resolver.ITypeResolver;

public abstract class AbstractTypeWhichImplementsInterfaceDataModelProvider
extends AbstractDataModelProvider {

private static final Logger LOGGER = Logger
.getLogger(AbstractTypeWhichImplementsInterfaceDataModelProvider.class.getName());

@Override
protected String[] getPatterns() {
return getInterfaceNames();
}

/**
* Returns the interface names to search.
*
* @return the interface names to search.
*/
protected abstract String[] getInterfaceNames();

@Override
public void collectDataModel(SearchMatch match, SearchContext context, IProgressMonitor monitor) {
Object element = match.getElement();
if (element instanceof IType type) {
try {
if (isApplicable(type)) {
processType(type, context, monitor);
}
} catch (Exception e) {
if (LOGGER.isLoggable(Level.SEVERE)) {

LOGGER.log(Level.SEVERE,
"Cannot collect Qute data model for the type '" + type.getElementName() + "'.", e);
}
}
}
}

private boolean isApplicable(IType type) throws JavaModelException {
String[] superInterfaceNames = type.getSuperInterfaceNames();
if (superInterfaceNames == null || superInterfaceNames.length == 0) {
return false;
}
for (String interfaceName : getInterfaceNames()) {
for (String superInterfaceName : superInterfaceNames) {
if (interfaceName.endsWith(superInterfaceName)) {
return true;
}
}
}
return false;
}

@Override
protected SearchPattern createSearchPattern(String interfaceName) {
return SearchPattern.createPattern(interfaceName, IJavaSearchConstants.CLASS, IJavaSearchConstants.IMPLEMENTORS,
SearchPattern.R_EXACT_MATCH);
}

protected abstract void processType(IType recordElement, SearchContext context, IProgressMonitor monitor)
throws JavaModelException;

}

0 comments on commit 426e102

Please sign in to comment.