Add nullability annotations to module/spring-boot-mustache

See gh-46587
This commit is contained in:
Moritz Halbritter 2025-08-04 11:50:36 +02:00
parent 1c13bbaa74
commit 38acdf3a3f
8 changed files with 65 additions and 26 deletions

View File

@ -23,6 +23,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.util.MimeType;
@ -51,12 +53,12 @@ public class MustacheProperties {
/**
* View names that can be resolved.
*/
private String[] viewNames;
private String @Nullable [] viewNames;
/**
* Name of the RequestContext attribute for all views.
*/
private String requestContextAttribute;
private @Nullable String requestContextAttribute;
/**
* Whether to enable MVC view resolution for Mustache.
@ -107,19 +109,19 @@ public class MustacheProperties {
this.suffix = suffix;
}
public String[] getViewNames() {
public String @Nullable [] getViewNames() {
return this.viewNames;
}
public void setViewNames(String[] viewNames) {
public void setViewNames(String @Nullable [] viewNames) {
this.viewNames = viewNames;
}
public String getRequestContextAttribute() {
public @Nullable String getRequestContextAttribute() {
return this.requestContextAttribute;
}
public void setRequestContextAttribute(String requestContextAttribute) {
public void setRequestContextAttribute(@Nullable String requestContextAttribute) {
this.requestContextAttribute = requestContextAttribute;
}
@ -128,7 +130,7 @@ public class MustacheProperties {
}
public String getCharsetName() {
return (this.charset != null) ? this.charset.name() : null;
return this.charset.name();
}
public void setCharset(Charset charset) {
@ -275,13 +277,13 @@ public class MustacheProperties {
/**
* Media types supported by Mustache views.
*/
private List<MediaType> mediaTypes;
private @Nullable List<MediaType> mediaTypes;
public List<MediaType> getMediaTypes() {
public @Nullable List<MediaType> getMediaTypes() {
return this.mediaTypes;
}
public void setMediaTypes(List<MediaType> mediaTypes) {
public void setMediaTypes(@Nullable List<MediaType> mediaTypes) {
this.mediaTypes = mediaTypes;
}

View File

@ -17,4 +17,7 @@
/**
* Auto-configuration for Mustache.
*/
@NullMarked
package org.springframework.boot.mustache.autoconfigure;
import org.jspecify.annotations.NullMarked;

View File

@ -28,14 +28,17 @@ import java.util.Optional;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Template;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.View;
import org.springframework.web.server.ServerWebExchange;
@ -48,9 +51,9 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class MustacheView extends AbstractUrlBasedView {
private Compiler compiler;
private @Nullable Compiler compiler;
private String charset;
private @Nullable String charset;
/**
* Set the JMustache compiler to be used by this view. Typically this property is not
@ -66,7 +69,7 @@ public class MustacheView extends AbstractUrlBasedView {
* Set the charset used for reading Mustache template files.
* @param charset the charset to use for reading template files
*/
public void setCharset(String charset) {
public void setCharset(@Nullable String charset) {
this.charset = charset;
}
@ -76,7 +79,8 @@ public class MustacheView extends AbstractUrlBasedView {
}
@Override
protected Mono<Void> renderInternal(Map<String, Object> model, MediaType contentType, ServerWebExchange exchange) {
protected Mono<Void> renderInternal(Map<String, Object> model, @Nullable MediaType contentType,
ServerWebExchange exchange) {
Resource resource = resolveResource();
if (resource == null) {
return Mono
@ -86,6 +90,7 @@ public class MustacheView extends AbstractUrlBasedView {
.bufferFactory()
.allocateBuffer(DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY);
try (Reader reader = getReader(resource)) {
Assert.state(this.compiler != null, "'compiler' must not be null");
Template template = this.compiler.compile(reader);
Charset charset = getCharset(contentType).orElseGet(this::getDefaultCharset);
try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset)) {
@ -100,8 +105,13 @@ public class MustacheView extends AbstractUrlBasedView {
return exchange.getResponse().writeWith(Flux.just(dataBuffer));
}
private Resource resolveResource() {
Resource resource = getApplicationContext().getResource(getUrl());
private @Nullable Resource resolveResource() {
ApplicationContext applicationContext = getApplicationContext();
String url = getUrl();
if (applicationContext == null || url == null) {
return null;
}
Resource resource = applicationContext.getResource(url);
if (resource == null || !resource.exists()) {
return null;
}
@ -115,7 +125,7 @@ public class MustacheView extends AbstractUrlBasedView {
return new InputStreamReader(resource.getInputStream());
}
private Optional<Charset> getCharset(MediaType mediaType) {
private Optional<Charset> getCharset(@Nullable MediaType mediaType) {
return Optional.ofNullable((mediaType != null) ? mediaType.getCharset() : null);
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.mustache.reactive.view;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import org.jspecify.annotations.Nullable;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
@ -34,7 +35,7 @@ public class MustacheViewResolver extends UrlBasedViewResolver {
private final Compiler compiler;
private String charset;
private @Nullable String charset;
/**
* Create a {@code MustacheViewResolver} backed by a default instance of a

View File

@ -18,4 +18,7 @@
* Additional {@link org.springframework.web.reactive.result.view.View Views} for use with
* WebFlux.
*/
@NullMarked
package org.springframework.boot.mustache.reactive.view;
import org.jspecify.annotations.NullMarked;

View File

@ -26,8 +26,11 @@ import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Template;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractTemplateView;
@ -41,9 +44,9 @@ import org.springframework.web.servlet.view.AbstractTemplateView;
*/
public class MustacheView extends AbstractTemplateView {
private Compiler compiler;
private @Nullable Compiler compiler;
private String charset;
private @Nullable String charset;
/**
* Set the Mustache compiler to be used by this view.
@ -61,27 +64,40 @@ public class MustacheView extends AbstractTemplateView {
* Set the charset used for reading Mustache template files.
* @param charset the charset to use for reading template files
*/
public void setCharset(String charset) {
public void setCharset(@Nullable String charset) {
this.charset = charset;
}
@Override
public boolean checkResource(Locale locale) throws Exception {
Resource resource = getApplicationContext().getResource(getUrl());
return (resource != null && resource.exists());
Resource resource = getResource();
return resource != null;
}
@Override
protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
Template template = createTemplate(getApplicationContext().getResource(getUrl()));
Resource resource = getResource();
Assert.state(resource != null, "'resource' must not be null");
Template template = createTemplate(resource);
if (template != null) {
template.execute(model, response.getWriter());
}
}
private @Nullable Resource getResource() {
ApplicationContext applicationContext = getApplicationContext();
String url = getUrl();
if (applicationContext == null || url == null) {
return null;
}
Resource resource = applicationContext.getResource(url);
return (resource.exists()) ? resource : null;
}
private Template createTemplate(Resource resource) throws IOException {
try (Reader reader = getReader(resource)) {
Assert.state(this.compiler != null, "'compiler' must not be null");
return this.compiler.compile(reader);
}
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.mustache.servlet.view;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
@ -33,7 +34,7 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
private final Mustache.Compiler compiler;
private String charset;
private @Nullable String charset;
/**
* Create a {@code MustacheViewResolver} backed by a default instance of a
@ -63,7 +64,7 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
* Set the charset.
* @param charset the charset
*/
public void setCharset(String charset) {
public void setCharset(@Nullable String charset) {
this.charset = charset;
}

View File

@ -17,4 +17,7 @@
/**
* Additional {@link org.springframework.web.servlet.View Views} for use with Web MVC.
*/
@NullMarked
package org.springframework.boot.mustache.servlet.view;
import org.jspecify.annotations.NullMarked;