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

View File

@ -17,4 +17,7 @@
/** /**
* Auto-configuration for Mustache. * Auto-configuration for Mustache.
*/ */
@NullMarked
package org.springframework.boot.mustache.autoconfigure; 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.Mustache.Compiler;
import com.samskivert.mustache.Template; import com.samskivert.mustache.Template;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType; 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.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.View; import org.springframework.web.reactive.result.view.View;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
@ -48,9 +51,9 @@ import org.springframework.web.server.ServerWebExchange;
*/ */
public class MustacheView extends AbstractUrlBasedView { 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 * 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. * Set the charset used for reading Mustache template files.
* @param charset the charset to use for reading 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; this.charset = charset;
} }
@ -76,7 +79,8 @@ public class MustacheView extends AbstractUrlBasedView {
} }
@Override @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(); Resource resource = resolveResource();
if (resource == null) { if (resource == null) {
return Mono return Mono
@ -86,6 +90,7 @@ public class MustacheView extends AbstractUrlBasedView {
.bufferFactory() .bufferFactory()
.allocateBuffer(DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY); .allocateBuffer(DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY);
try (Reader reader = getReader(resource)) { try (Reader reader = getReader(resource)) {
Assert.state(this.compiler != null, "'compiler' must not be null");
Template template = this.compiler.compile(reader); Template template = this.compiler.compile(reader);
Charset charset = getCharset(contentType).orElseGet(this::getDefaultCharset); Charset charset = getCharset(contentType).orElseGet(this::getDefaultCharset);
try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset)) { try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset)) {
@ -100,8 +105,13 @@ public class MustacheView extends AbstractUrlBasedView {
return exchange.getResponse().writeWith(Flux.just(dataBuffer)); return exchange.getResponse().writeWith(Flux.just(dataBuffer));
} }
private Resource resolveResource() { private @Nullable Resource resolveResource() {
Resource resource = getApplicationContext().getResource(getUrl()); ApplicationContext applicationContext = getApplicationContext();
String url = getUrl();
if (applicationContext == null || url == null) {
return null;
}
Resource resource = applicationContext.getResource(url);
if (resource == null || !resource.exists()) { if (resource == null || !resource.exists()) {
return null; return null;
} }
@ -115,7 +125,7 @@ public class MustacheView extends AbstractUrlBasedView {
return new InputStreamReader(resource.getInputStream()); 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); 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;
import com.samskivert.mustache.Mustache.Compiler; 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.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.UrlBasedViewResolver; import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
@ -34,7 +35,7 @@ public class MustacheViewResolver extends UrlBasedViewResolver {
private final Compiler compiler; private final Compiler compiler;
private String charset; private @Nullable String charset;
/** /**
* Create a {@code MustacheViewResolver} backed by a default instance of a * 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 * Additional {@link org.springframework.web.reactive.result.view.View Views} for use with
* WebFlux. * WebFlux.
*/ */
@NullMarked
package org.springframework.boot.mustache.reactive.view; 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 com.samskivert.mustache.Template;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractTemplateView; import org.springframework.web.servlet.view.AbstractTemplateView;
@ -41,9 +44,9 @@ import org.springframework.web.servlet.view.AbstractTemplateView;
*/ */
public class MustacheView extends 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. * 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. * Set the charset used for reading Mustache template files.
* @param charset the charset to use for reading 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; this.charset = charset;
} }
@Override @Override
public boolean checkResource(Locale locale) throws Exception { public boolean checkResource(Locale locale) throws Exception {
Resource resource = getApplicationContext().getResource(getUrl()); Resource resource = getResource();
return (resource != null && resource.exists()); return resource != null;
} }
@Override @Override
protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request, protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception { 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) { if (template != null) {
template.execute(model, response.getWriter()); 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 { private Template createTemplate(Resource resource) throws IOException {
try (Reader reader = getReader(resource)) { try (Reader reader = getReader(resource)) {
Assert.state(this.compiler != null, "'compiler' must not be null");
return this.compiler.compile(reader); 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;
import com.samskivert.mustache.Mustache.Compiler; import com.samskivert.mustache.Mustache.Compiler;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver; import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
@ -33,7 +34,7 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
private final Mustache.Compiler compiler; private final Mustache.Compiler compiler;
private String charset; private @Nullable String charset;
/** /**
* Create a {@code MustacheViewResolver} backed by a default instance of a * Create a {@code MustacheViewResolver} backed by a default instance of a
@ -63,7 +64,7 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
* Set the charset. * Set the charset.
* @param charset the charset * @param charset the charset
*/ */
public void setCharset(String charset) { public void setCharset(@Nullable String charset) {
this.charset = charset; this.charset = charset;
} }

View File

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