From 2a32c6cf5776d52b6faa6b03712b196b168002a3 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 30 Jul 2018 22:08:11 +0200 Subject: [PATCH] Nullability refinements in spring-webmvc Includes revision of web.servlet.tags.form for non-null conventions. Issue: SPR-15540 (cherry picked from commit f74a631ea1714e1448c2c374bf83a2f48da1c992) --- .../java/org/springframework/ui/ModelMap.java | 8 ++-- .../web/servlet/ModelAndView.java | 24 +++++------ .../support/RequestDataValueProcessor.java | 9 ++-- .../tags/form/AbstractCheckedElementTag.java | 16 ++++--- .../form/AbstractDataBoundFormElementTag.java | 15 ++++--- .../servlet/tags/form/AbstractFormTag.java | 10 ++--- .../tags/form/AbstractHtmlElementBodyTag.java | 11 +++-- .../tags/form/AbstractHtmlElementTag.java | 43 +++++++++++++++++-- .../form/AbstractHtmlInputElementTag.java | 12 +++++- .../form/AbstractMultiCheckedElementTag.java | 24 ++++++++--- .../form/AbstractSingleCheckedElementTag.java | 17 +++++--- .../web/servlet/tags/form/ButtonTag.java | 16 +++++-- .../web/servlet/tags/form/ErrorsTag.java | 6 ++- .../web/servlet/tags/form/FormTag.java | 34 ++++++++++++--- .../web/servlet/tags/form/InputTag.java | 31 +++++++++---- .../web/servlet/tags/form/LabelTag.java | 10 +++-- .../web/servlet/tags/form/OptionTag.java | 16 ++++--- .../web/servlet/tags/form/OptionWriter.java | 37 +++++++++------- .../web/servlet/tags/form/OptionsTag.java | 15 +++++-- .../web/servlet/tags/form/RadioButtonTag.java | 2 +- .../web/servlet/tags/form/SelectTag.java | 22 +++++++--- .../tags/form/SelectedValueComparator.java | 37 ++++++++-------- .../web/servlet/tags/form/TagWriter.java | 9 ++-- .../web/servlet/tags/form/TextareaTag.java | 10 ++++- .../web/servlet/tags/form/ValueFormatter.java | 9 ++-- .../web/servlet/tags/form/package-info.java | 8 +++- 26 files changed, 315 insertions(+), 136 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/ui/ModelMap.java b/spring-context/src/main/java/org/springframework/ui/ModelMap.java index 04c7a69bbc..3e37791ca2 100644 --- a/spring-context/src/main/java/org/springframework/ui/ModelMap.java +++ b/spring-context/src/main/java/org/springframework/ui/ModelMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ public class ModelMap extends LinkedHashMap { * under the supplied name. * @see #addAttribute(String, Object) */ - public ModelMap(String attributeName, Object attributeValue) { + public ModelMap(String attributeName, @Nullable Object attributeValue) { addAttribute(attributeName, attributeValue); } @@ -80,10 +80,10 @@ public class ModelMap extends LinkedHashMap { /** * Add the supplied attribute to this {@code Map} using a * {@link org.springframework.core.Conventions#getVariableName generated name}. - *

Note: Empty {@link Collection Collections} are not added to + *

Note: Empty {@link Collection Collections} are not added to * the model when using this method because we cannot correctly determine * the true convention name. View code should check for {@code null} rather - * than for empty collections as is already done by JSTL tags. + * than for empty collections as is already done by JSTL tags. * @param attributeValue the model attribute value (never {@code null}) */ public ModelMap addAttribute(Object attributeValue) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java index 6902e70af8..79387b8de7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,7 +85,7 @@ public class ModelAndView { /** * Convenient constructor when there is no model data to expose. * Can also be used in conjunction with {@code addObject}. - * @param view View object to render + * @param view the View object to render * @see #addObject */ public ModelAndView(View view) { @@ -96,7 +96,7 @@ public class ModelAndView { * Create a new ModelAndView given a view name and a model. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver - * @param model Map of model names (Strings) to model objects + * @param model a Map of model names (Strings) to model objects * (Objects). Model entries may not be {@code null}, but the * model Map may be {@code null} if there is no model data. */ @@ -109,11 +109,11 @@ public class ModelAndView { /** * Create a new ModelAndView given a View object and a model. - * Note: the supplied model data is copied into the internal + * Note: the supplied model data is copied into the internal * storage of this class. You should not consider to modify the supplied - * Map after supplying it to this class - * @param view View object to render - * @param model Map of model names (Strings) to model objects + * Map after supplying it to this class + * @param view the View object to render + * @param model a Map of model names (Strings) to model objects * (Objects). Model entries may not be {@code null}, but the * model Map may be {@code null} if there is no model data. */ @@ -141,7 +141,7 @@ public class ModelAndView { * Create a new ModelAndView given a view name, model, and HTTP status. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver - * @param model Map of model names (Strings) to model objects + * @param model a Map of model names (Strings) to model objects * (Objects). Model entries may not be {@code null}, but the * model Map may be {@code null} if there is no model data. * @param status an HTTP status code to use for the response @@ -170,7 +170,7 @@ public class ModelAndView { /** * Convenient constructor to take a single model object. - * @param view View object to render + * @param view the View object to render * @param modelName name of the single entry in the model * @param modelObject the single model object */ @@ -280,12 +280,12 @@ public class ModelAndView { /** * Add an attribute to the model. - * @param attributeName name of the object to add to the model - * @param attributeValue object to add to the model (never {@code null}) + * @param attributeName name of the object to add to the model (never {@code null}) + * @param attributeValue object to add to the model (can be {@code null}) * @see ModelMap#addAttribute(String, Object) * @see #getModelMap() */ - public ModelAndView addObject(String attributeName, Object attributeValue) { + public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) { getModelMap().addAttribute(attributeName, attributeValue); return this; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java index 967db3b9aa..ac908a323a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestDataValueProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.web.servlet.support; import java.util.Map; - import javax.servlet.http.HttpServletRequest; import org.springframework.lang.Nullable; @@ -52,17 +51,17 @@ public interface RequestDataValueProcessor { /** * Invoked when a form field value is rendered. * @param request the current request - * @param name the form field name + * @param name the form field name (if any) * @param value the form field value * @param type the form field type ("text", "hidden", etc.) * @return the form field value to use, possibly modified */ - String processFormFieldValue(HttpServletRequest request, String name, String value, String type); + String processFormFieldValue(HttpServletRequest request, @Nullable String name, String value, String type); /** * Invoked after all form fields have been rendered. * @param request the current request - * @return additional hidden form fields to be added, or {@code null} + * @return additional hidden form fields to be added, or {@code null} if none */ @Nullable Map getExtraHiddenFields(HttpServletRequest request); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java index a1035d4da1..e4835958e7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; + /** * Abstract base class to provide common methods for * implementing databinding-aware JSP tags for rendering an HTML '{@code input}' @@ -36,7 +38,7 @@ public abstract class AbstractCheckedElementTag extends AbstractHtmlInputElement * '{@code input}' element as 'checked' if the supplied value matches the * bound value. */ - protected void renderFromValue(Object value, TagWriter tagWriter) throws JspException { + protected void renderFromValue(@Nullable Object value, TagWriter tagWriter) throws JspException { renderFromValue(value, value, tagWriter); } @@ -45,7 +47,9 @@ public abstract class AbstractCheckedElementTag extends AbstractHtmlInputElement * '{@code input}' element as 'checked' if the supplied value matches the * bound value. */ - protected void renderFromValue(Object item, Object value, TagWriter tagWriter) throws JspException { + protected void renderFromValue(@Nullable Object item, @Nullable Object value, TagWriter tagWriter) + throws JspException { + String displayValue = convertToDisplayString(value); tagWriter.writeAttribute("value", processFieldValue(getName(), displayValue, getInputType())); if (isOptionSelected(value) || (value != item && isOptionSelected(item))) { @@ -57,7 +61,7 @@ public abstract class AbstractCheckedElementTag extends AbstractHtmlInputElement * Determines whether the supplied value matched the selected value * through delegating to {@link SelectedValueComparator#isSelected}. */ - private boolean isOptionSelected(Object value) throws JspException { + private boolean isOptionSelected(@Nullable Object value) throws JspException { return SelectedValueComparator.isSelected(getBindStatus(), value); } @@ -77,8 +81,10 @@ public abstract class AbstractCheckedElementTag extends AbstractHtmlInputElement * Return a unique ID for the bound name within the current PageContext. */ @Override + @Nullable protected String autogenerateId() throws JspException { - return TagIdGenerator.nextId(super.autogenerateId(), this.pageContext); + String id = super.autogenerateId(); + return (id != null ? TagIdGenerator.nextId(id, this.pageContext) : null); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractDataBoundFormElementTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractDataBoundFormElementTag.java index 1cce7c6e6e..a6b71211c7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractDataBoundFormElementTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractDataBoundFormElementTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,16 +55,19 @@ public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag im /** * The property path from the {@link FormTag#setModelAttribute form object}. */ + @Nullable private String path; /** * The value of the '{@code id}' attribute. */ + @Nullable private String id; /** * The {@link BindStatus} of this tag. */ + @Nullable private BindStatus bindStatus; @@ -91,7 +94,7 @@ public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag im * Note that the default value may not be valid for certain tags. */ @Override - public void setId(String id) { + public void setId(@Nullable String id) { this.id = id; } @@ -99,6 +102,7 @@ public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag im * Get the value of the '{@code id}' attribute. */ @Override + @Nullable public String getId() { return this.id; } @@ -179,6 +183,7 @@ public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag im * Get the value of the nested path that may have been exposed by the * {@link NestedPathTag}. */ + @Nullable protected String getNestedPath() { return (String) this.pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); } @@ -225,7 +230,7 @@ public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag im * Get a display String for the given value, converted by a PropertyEditor * that the BindStatus may have registered for the value's Class. */ - protected String convertToDisplayString(Object value) throws JspException { + protected String convertToDisplayString(@Nullable Object value) throws JspException { PropertyEditor editor = (value != null ? getBindStatus().findEditor(value.getClass()) : null); return getDisplayString(value, editor); } @@ -234,10 +239,10 @@ public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag im * Process the given form field through a {@link RequestDataValueProcessor} * instance if one is configured or otherwise returns the same value. */ - protected final String processFieldValue(String name, String value, String type) { + protected final String processFieldValue(@Nullable String name, String value, String type) { RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor(); ServletRequest request = this.pageContext.getRequest(); - if (processor != null && (request instanceof HttpServletRequest)) { + if (processor != null && request instanceof HttpServletRequest) { value = processor.processFormFieldValue((HttpServletRequest) request, name, value, type); } return value; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractFormTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractFormTag.java index 1fbc7e64f4..1fc612e70e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractFormTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractFormTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.web.servlet.tags.form; import java.beans.PropertyEditor; - import javax.servlet.jsp.JspException; import org.springframework.lang.Nullable; @@ -46,7 +45,8 @@ public abstract class AbstractFormTag extends HtmlEscapingAwareTag { * Evaluate the supplied value for the supplied attribute name. *

The default implementation simply returns the given value as-is. */ - protected Object evaluate(String attributeName, Object value) throws JspException { + @Nullable + protected Object evaluate(String attributeName, @Nullable Object value) throws JspException { return value; } @@ -90,7 +90,7 @@ public abstract class AbstractFormTag extends HtmlEscapingAwareTag { * Get the display value of the supplied {@code Object}, HTML escaped * as required. This version is not {@link PropertyEditor}-aware. */ - protected String getDisplayString(Object value) { + protected String getDisplayString(@Nullable Object value) { return ValueFormatter.getDisplayString(value, isHtmlEscape()); } @@ -100,7 +100,7 @@ public abstract class AbstractFormTag extends HtmlEscapingAwareTag { * {@link PropertyEditor} is not null then the {@link PropertyEditor} is used * to obtain the display value. */ - protected String getDisplayString(Object value, PropertyEditor propertyEditor) { + protected String getDisplayString(@Nullable Object value, @Nullable PropertyEditor propertyEditor) { return ValueFormatter.getDisplayString(value, propertyEditor, isHtmlEscape()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java index ae4d30755f..1d9f7951f4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTag; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -35,8 +37,10 @@ import org.springframework.util.StringUtils; @SuppressWarnings("serial") public abstract class AbstractHtmlElementBodyTag extends AbstractHtmlElementTag implements BodyTag { + @Nullable private BodyContent bodyContent; + @Nullable private TagWriter tagWriter; @@ -57,11 +61,12 @@ public abstract class AbstractHtmlElementBodyTag extends AbstractHtmlElementTag * If {@link #shouldRender rendering}, flush any buffered * {@link BodyContent} or, if no {@link BodyContent} is supplied, * {@link #renderDefaultContent render the default content}. - * @return Tag#EVAL_PAGE + * @return a {@link javax.servlet.jsp.tagext.Tag#EVAL_PAGE} result */ @Override public int doEndTag() throws JspException { if (shouldRender()) { + Assert.state(this.tagWriter != null, "No TagWriter set"); if (this.bodyContent != null && StringUtils.hasText(this.bodyContent.getString())) { renderFromBodyContent(this.bodyContent, this.tagWriter); } @@ -79,7 +84,7 @@ public abstract class AbstractHtmlElementBodyTag extends AbstractHtmlElementTag * override this to add additional content to the output. */ protected void renderFromBodyContent(BodyContent bodyContent, TagWriter tagWriter) throws JspException { - flushBufferedBodyContent(this.bodyContent); + flushBufferedBodyContent(bodyContent); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java index bdf1dda747..52175b0106 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.Map; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.DynamicAttributes; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -76,40 +77,58 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen public static final String ONKEYDOWN_ATTRIBUTE = "onkeydown"; + @Nullable private String cssClass; + @Nullable private String cssErrorClass; + @Nullable private String cssStyle; + @Nullable private String lang; + @Nullable private String title; + @Nullable private String dir; + @Nullable private String tabindex; + @Nullable private String onclick; + @Nullable private String ondblclick; + @Nullable private String onmousedown; + @Nullable private String onmouseup; + @Nullable private String onmouseover; + @Nullable private String onmousemove; + @Nullable private String onmouseout; + @Nullable private String onkeypress; + @Nullable private String onkeyup; + @Nullable private String onkeydown; + @Nullable private Map dynamicAttributes; @@ -125,6 +144,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code class}' attribute. * May be a runtime expression. */ + @Nullable protected String getCssClass() { return this.cssClass; } @@ -141,6 +161,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * The CSS class to use when the field bound to a particular tag has errors. * May be a runtime expression. */ + @Nullable protected String getCssErrorClass() { return this.cssErrorClass; } @@ -157,6 +178,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code style}' attribute. * May be a runtime expression. */ + @Nullable protected String getCssStyle() { return this.cssStyle; } @@ -173,6 +195,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code lang}' attribute. * May be a runtime expression. */ + @Nullable protected String getLang() { return this.lang; } @@ -189,6 +212,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code title}' attribute. * May be a runtime expression. */ + @Nullable protected String getTitle() { return this.title; } @@ -205,6 +229,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code dir}' attribute. * May be a runtime expression. */ + @Nullable protected String getDir() { return this.dir; } @@ -221,6 +246,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code tabindex}' attribute. * May be a runtime expression. */ + @Nullable protected String getTabindex() { return this.tabindex; } @@ -237,6 +263,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onclick}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnclick() { return this.onclick; } @@ -253,6 +280,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code ondblclick}' attribute. * May be a runtime expression. */ + @Nullable protected String getOndblclick() { return this.ondblclick; } @@ -269,6 +297,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onmousedown}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnmousedown() { return this.onmousedown; } @@ -285,6 +314,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onmouseup}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnmouseup() { return this.onmouseup; } @@ -301,6 +331,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onmouseover}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnmouseover() { return this.onmouseover; } @@ -317,6 +348,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onmousemove}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnmousemove() { return this.onmousemove; } @@ -332,6 +364,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onmouseout}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnmouseout() { return this.onmouseout; } @@ -348,6 +381,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onkeypress}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnkeypress() { return this.onkeypress; } @@ -364,6 +398,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onkeyup}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnkeyup() { return this.onkeyup; } @@ -380,6 +415,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * Get the value of the '{@code onkeydown}' attribute. * May be a runtime expression. */ + @Nullable protected String getOnkeydown() { return this.onkeydown; } @@ -387,6 +423,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen /** * Get the map of dynamic attributes. */ + @Nullable protected Map getDynamicAttributes() { return this.dynamicAttributes; } @@ -395,7 +432,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen * {@inheritDoc} */ @Override - public void setDynamicAttribute(String uri, String localName, Object value ) throws JspException { + public void setDynamicAttribute(String uri, String localName, Object value) throws JspException { if (this.dynamicAttributes == null) { this.dynamicAttributes = new HashMap<>(); } @@ -403,7 +440,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen throw new IllegalArgumentException( "Attribute " + localName + "=\"" + value + "\" is not allowed"); } - dynamicAttributes.put(localName, value); + this.dynamicAttributes.put(localName, value); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlInputElementTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlInputElementTag.java index 90e94823aa..285db7e15c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlInputElementTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlInputElementTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; + /** * Base class for databinding-aware JSP tags that render HTML form input element. * @@ -63,12 +65,16 @@ public abstract class AbstractHtmlInputElementTag extends AbstractHtmlElementTag public static final String READONLY_ATTRIBUTE = "readonly"; + @Nullable private String onfocus; + @Nullable private String onblur; + @Nullable private String onchange; + @Nullable private String accesskey; private boolean disabled; @@ -87,6 +93,7 @@ public abstract class AbstractHtmlInputElementTag extends AbstractHtmlElementTag /** * Get the value of the '{@code onfocus}' attribute. */ + @Nullable protected String getOnfocus() { return this.onfocus; } @@ -102,6 +109,7 @@ public abstract class AbstractHtmlInputElementTag extends AbstractHtmlElementTag /** * Get the value of the '{@code onblur}' attribute. */ + @Nullable protected String getOnblur() { return this.onblur; } @@ -117,6 +125,7 @@ public abstract class AbstractHtmlInputElementTag extends AbstractHtmlElementTag /** * Get the value of the '{@code onchange}' attribute. */ + @Nullable protected String getOnchange() { return this.onchange; } @@ -132,6 +141,7 @@ public abstract class AbstractHtmlInputElementTag extends AbstractHtmlElementTag /** * Get the value of the '{@code accesskey}' attribute. */ + @Nullable protected String getAccesskey() { return this.accesskey; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractMultiCheckedElementTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractMultiCheckedElementTag.java index 58d4cc79fd..ce119a2cc1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractMultiCheckedElementTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractMultiCheckedElementTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import javax.servlet.jsp.JspException; import org.springframework.beans.BeanWrapper; import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -50,17 +51,20 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem * The {@link java.util.Collection}, {@link java.util.Map} or array of objects * used to generate the '{@code input type="checkbox/radio"}' tags. */ + @Nullable private Object items; /** * The name of the property mapped to the '{@code value}' attribute * of the '{@code input type="checkbox/radio"}' tag. */ + @Nullable private String itemValue; /** * The value to be displayed as part of the '{@code input type="checkbox/radio"}' tag. */ + @Nullable private String itemLabel; /** @@ -71,6 +75,7 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem /** * Delimiter to use between each '{@code input type="checkbox/radio"}' tags. */ + @Nullable private String delimiter; @@ -89,6 +94,7 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem * Get the {@link java.util.Collection}, {@link java.util.Map} or array of objects * used to generate the '{@code input type="checkbox/radio"}' tags. */ + @Nullable protected Object getItems() { return this.items; } @@ -107,6 +113,7 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem * Get the name of the property mapped to the '{@code value}' attribute * of the '{@code input type="checkbox/radio"}' tag. */ + @Nullable protected String getItemValue() { return this.itemValue; } @@ -125,6 +132,7 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem * Get the value to be displayed as part of the * '{@code input type="checkbox/radio"}' tag. */ + @Nullable protected String getItemLabel() { return this.itemLabel; } @@ -142,6 +150,7 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem * Return the delimiter to be used between each * '{@code input type="radio"}' tag. */ + @Nullable public String getDelimiter() { return this.delimiter; } @@ -236,8 +245,8 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem return SKIP_BODY; } - private void writeObjectEntry(TagWriter tagWriter, String valueProperty, - String labelProperty, Object item, int itemIndex) throws JspException { + private void writeObjectEntry(TagWriter tagWriter, @Nullable String valueProperty, + @Nullable String labelProperty, Object item, int itemIndex) throws JspException { BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(item); Object renderValue; @@ -254,8 +263,8 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem writeElementTag(tagWriter, item, renderValue, renderLabel, itemIndex); } - private void writeMapEntry(TagWriter tagWriter, String valueProperty, - String labelProperty, Map.Entry entry, int itemIndex) throws JspException { + private void writeMapEntry(TagWriter tagWriter, @Nullable String valueProperty, + @Nullable String labelProperty, Map.Entry entry, int itemIndex) throws JspException { Object mapKey = entry.getKey(); Object mapValue = entry.getValue(); @@ -268,8 +277,8 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem writeElementTag(tagWriter, mapKey, renderValue, renderLabel, itemIndex); } - private void writeElementTag(TagWriter tagWriter, Object item, Object value, Object label, int itemIndex) - throws JspException { + private void writeElementTag(TagWriter tagWriter, Object item, @Nullable Object value, + @Nullable Object label, int itemIndex) throws JspException { tagWriter.startTag(getElement()); if (itemIndex > 0) { @@ -280,6 +289,7 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem } tagWriter.startTag("input"); String id = resolveId(); + Assert.state(id != null, "Attribute 'id' is required"); writeOptionalAttribute(tagWriter, "id", id); writeOptionalAttribute(tagWriter, "name", getName()); writeOptionalAttributes(tagWriter); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractSingleCheckedElementTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractSingleCheckedElementTag.java index 9d26dc554a..4bbcfbb411 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractSingleCheckedElementTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractSingleCheckedElementTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,13 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + /** - * Abstract base class to provide common methods for implementing - * databinding-aware JSP tags for rendering a single - * HTML '{@code input}' element with a '{@code type}' - * of '{@code checkbox}' or '{@code radio}'. + * Abstract base class to provide common methods for implementing databinding-aware + * JSP tags for rendering a single HTML '{@code input}' element with a + * '{@code type}' of '{@code checkbox}' or '{@code radio}'. * * @author Juergen Hoeller * @since 2.5.2 @@ -33,11 +35,13 @@ public abstract class AbstractSingleCheckedElementTag extends AbstractCheckedEle /** * The value of the '{@code value}' attribute. */ + @Nullable private Object value; /** * The value of the '{@code label}' attribute. */ + @Nullable private Object label; @@ -52,6 +56,7 @@ public abstract class AbstractSingleCheckedElementTag extends AbstractCheckedEle /** * Get the value of the '{@code value}' attribute. */ + @Nullable protected Object getValue() { return this.value; } @@ -67,6 +72,7 @@ public abstract class AbstractSingleCheckedElementTag extends AbstractCheckedEle /** * Get the value of the '{@code label}' attribute. */ + @Nullable protected Object getLabel() { return this.label; } @@ -89,6 +95,7 @@ public abstract class AbstractSingleCheckedElementTag extends AbstractCheckedEle Object resolvedLabel = evaluate("label", getLabel()); if (resolvedLabel != null) { + Assert.state(id != null, "Label id is required"); tagWriter.startTag("label"); tagWriter.writeAttribute("for", id); tagWriter.appendValue(convertToDisplayString(resolvedLabel)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ButtonTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ButtonTag.java index f762b374cd..8ab65e1811 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ButtonTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ButtonTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.web.servlet.support.RequestDataValueProcessor; /** @@ -77,10 +79,13 @@ public class ButtonTag extends AbstractHtmlElementTag { public static final String DISABLED_ATTRIBUTE = "disabled"; + @Nullable private TagWriter tagWriter; + @Nullable private String name; + @Nullable private String value; private boolean disabled; @@ -97,6 +102,7 @@ public class ButtonTag extends AbstractHtmlElementTag { * Set the value of the '{@code name}' attribute. */ @Override + @Nullable public String getName() { return this.name; } @@ -104,13 +110,14 @@ public class ButtonTag extends AbstractHtmlElementTag { /** * Set the value of the '{@code value}' attribute. */ - public void setValue(String value) { + public void setValue(@Nullable String value) { this.value = value; } /** * Get the value of the '{@code value}' attribute. */ + @Nullable public String getValue() { return this.value; } @@ -150,13 +157,13 @@ public class ButtonTag extends AbstractHtmlElementTag { * when the value is written. */ protected void writeValue(TagWriter tagWriter) throws JspException { - String valueToUse = (getValue() != null) ? getValue() : getDefaultValue(); + String valueToUse = (getValue() != null ? getValue() : getDefaultValue()); tagWriter.writeAttribute("value", processFieldValue(getName(), valueToUse, getType())); } /** * Return the default value. - * @return The default value if none supplied. + * @return the default value if none supplied */ protected String getDefaultValue() { return "Submit"; @@ -176,6 +183,7 @@ public class ButtonTag extends AbstractHtmlElementTag { */ @Override public int doEndTag() throws JspException { + Assert.state(this.tagWriter != null, "No TagWriter set"); this.tagWriter.endTag(); return EVAL_PAGE; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ErrorsTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ErrorsTag.java index 99245978fe..601db220c1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ErrorsTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/ErrorsTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,11 @@ package org.springframework.web.servlet.tags.form; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyTag; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -210,6 +210,7 @@ public class ErrorsTag extends AbstractHtmlElementBodyTag implements BodyTag { /** * Stores any value that existed in the 'errors messages' before the tag was started. */ + @Nullable private Object oldMessages; private boolean errorMessagesWereExposed; @@ -271,6 +272,7 @@ public class ErrorsTag extends AbstractHtmlElementBodyTag implements BodyTag { * is not a validate attribute for the '{@code span}' element. */ @Override + @Nullable protected String getName() throws JspException { return null; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java index 7782f35eac..1753f1eeda 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java @@ -28,6 +28,8 @@ import javax.servlet.jsp.PageContext; import org.springframework.beans.PropertyAccessor; import org.springframework.core.Conventions; import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -288,33 +290,44 @@ public class FormTag extends AbstractHtmlElementTag { private static final String TYPE_ATTRIBUTE = "type"; + @Nullable private TagWriter tagWriter; private String modelAttribute = DEFAULT_COMMAND_NAME; + @Nullable private String name; + @Nullable private String action; + @Nullable private String servletRelativeAction; private String method = DEFAULT_METHOD; + @Nullable private String target; + @Nullable private String enctype; + @Nullable private String acceptCharset; + @Nullable private String onsubmit; + @Nullable private String onreset; + @Nullable private String autocomplete; private String methodParam = DEFAULT_METHOD_PARAM; - /** Caching a previous nested path, so that it may be reset */ + /** Caching a previous nested path, so that it may be reset. */ + @Nullable private String previousNestedPath; @@ -347,6 +360,7 @@ public class FormTag extends AbstractHtmlElementTag { * Get the value of the '{@code name}' attribute. */ @Override + @Nullable protected String getName() throws JspException { return this.name; } @@ -355,13 +369,14 @@ public class FormTag extends AbstractHtmlElementTag { * Set the value of the '{@code action}' attribute. *

May be a runtime expression. */ - public void setAction(String action) { + public void setAction(@Nullable String action) { this.action = (action != null ? action : ""); } /** * Get the value of the '{@code action}' attribute. */ + @Nullable protected String getAction() { return this.action; } @@ -372,14 +387,15 @@ public class FormTag extends AbstractHtmlElementTag { *

May be a runtime expression. * @since 3.2.3 */ - public void setServletRelativeAction(String servletRelativeAction) { - this.servletRelativeAction = (servletRelativeAction != null ? servletRelativeAction : ""); + public void setServletRelativeAction(@Nullable String servletRelativeAction) { + this.servletRelativeAction = servletRelativeAction; } /** * Get the servlet-relative value of the '{@code action}' attribute. * @since 3.2.3 */ + @Nullable protected String getServletRelativeAction() { return this.servletRelativeAction; } @@ -410,6 +426,7 @@ public class FormTag extends AbstractHtmlElementTag { /** * Get the value of the '{@code target}' attribute. */ + @Nullable public String getTarget() { return this.target; } @@ -425,6 +442,7 @@ public class FormTag extends AbstractHtmlElementTag { /** * Get the value of the '{@code enctype}' attribute. */ + @Nullable protected String getEnctype() { return this.enctype; } @@ -440,6 +458,7 @@ public class FormTag extends AbstractHtmlElementTag { /** * Get the value of the '{@code acceptCharset}' attribute. */ + @Nullable protected String getAcceptCharset() { return this.acceptCharset; } @@ -455,6 +474,7 @@ public class FormTag extends AbstractHtmlElementTag { /** * Get the value of the '{@code onsubmit}' attribute. */ + @Nullable protected String getOnsubmit() { return this.onsubmit; } @@ -470,6 +490,7 @@ public class FormTag extends AbstractHtmlElementTag { /** * Get the value of the '{@code onreset}' attribute. */ + @Nullable protected String getOnreset() { return this.onreset; } @@ -485,6 +506,7 @@ public class FormTag extends AbstractHtmlElementTag { /** * Get the value of the '{@code autocomplete}' attribute. */ + @Nullable protected String getAutocomplete() { return this.autocomplete; } @@ -670,6 +692,7 @@ public class FormTag extends AbstractHtmlElementTag { if (processor != null && request instanceof HttpServletRequest) { writeHiddenFields(processor.getExtraHiddenFields((HttpServletRequest) request)); } + Assert.state(this.tagWriter != null, "No TagWriter set"); this.tagWriter.endTag(); return EVAL_PAGE; } @@ -677,8 +700,9 @@ public class FormTag extends AbstractHtmlElementTag { /** * Writes the given values as hidden fields. */ - private void writeHiddenFields(Map hiddenFields) throws JspException { + private void writeHiddenFields(@Nullable Map hiddenFields) throws JspException { if (!CollectionUtils.isEmpty(hiddenFields)) { + Assert.state(this.tagWriter != null, "No TagWriter set"); this.tagWriter.appendValue("

\n"); for (String name : hiddenFields.keySet()) { this.tagWriter.appendValue("} tag renders an HTML 'input' tag with type 'text' using * the bound value. @@ -241,19 +244,22 @@ public class InputTag extends AbstractHtmlInputElementTag { public static final String ONSELECT_ATTRIBUTE = "onselect"; - public static final String READONLY_ATTRIBUTE = "readonly"; - public static final String AUTOCOMPLETE_ATTRIBUTE = "autocomplete"; + @Nullable private String size; + @Nullable private String maxlength; + @Nullable private String alt; + @Nullable private String onselect; + @Nullable private String autocomplete; @@ -268,6 +274,7 @@ public class InputTag extends AbstractHtmlInputElementTag { /** * Get the value of the '{@code size}' attribute. */ + @Nullable protected String getSize() { return this.size; } @@ -283,6 +290,7 @@ public class InputTag extends AbstractHtmlInputElementTag { /** * Get the value of the '{@code maxlength}' attribute. */ + @Nullable protected String getMaxlength() { return this.maxlength; } @@ -298,6 +306,7 @@ public class InputTag extends AbstractHtmlInputElementTag { /** * Get the value of the '{@code alt}' attribute. */ + @Nullable protected String getAlt() { return this.alt; } @@ -313,6 +322,7 @@ public class InputTag extends AbstractHtmlInputElementTag { /** * Get the value of the '{@code onselect}' attribute. */ + @Nullable protected String getOnselect() { return this.onselect; } @@ -328,6 +338,7 @@ public class InputTag extends AbstractHtmlInputElementTag { /** * Get the value of the '{@code autocomplete}' attribute. */ + @Nullable protected String getAutocomplete() { return this.autocomplete; } @@ -343,7 +354,8 @@ public class InputTag extends AbstractHtmlInputElementTag { tagWriter.startTag("input"); writeDefaultAttributes(tagWriter); - if (!hasDynamicTypeAttribute()) { + Map attributes = getDynamicAttributes(); + if (attributes == null || !attributes.containsKey("type")) { tagWriter.writeAttribute("type", getType()); } writeValue(tagWriter); @@ -359,10 +371,6 @@ public class InputTag extends AbstractHtmlInputElementTag { return SKIP_BODY; } - private boolean hasDynamicTypeAttribute() { - return getDynamicAttributes() != null && getDynamicAttributes().containsKey("type"); - } - /** * Writes the '{@code value}' attribute to the supplied {@link TagWriter}. * Subclasses may choose to override this implementation to control exactly @@ -370,7 +378,14 @@ public class InputTag extends AbstractHtmlInputElementTag { */ protected void writeValue(TagWriter tagWriter) throws JspException { String value = getDisplayString(getBoundValue(), getPropertyEditor()); - String type = hasDynamicTypeAttribute() ? (String) getDynamicAttributes().get("type") : getType(); + String type = null; + Map attributes = getDynamicAttributes(); + if (attributes != null) { + type = (String) getDynamicAttributes().get("type"); + } + if (type == null) { + type = getType(); + } tagWriter.writeAttribute("value", processFieldValue(getName(), value, type)); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/LabelTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/LabelTag.java index b74c927179..6a57b9ce1e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/LabelTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/LabelTag.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -190,21 +191,21 @@ public class LabelTag extends AbstractHtmlElementTag { * The {@link TagWriter} instance being used. *

Stored so we can close the tag on {@link #doEndTag()}. */ + @Nullable private TagWriter tagWriter; /** * The value of the '{@code for}' attribute. */ + @Nullable private String forId; /** * Set the value of the '{@code for}' attribute. *

Defaults to the value of {@link #getPath}; may be a runtime expression. - * @throws IllegalArgumentException if the supplied value is {@code null} */ public void setFor(String forId) { - Assert.notNull(forId, "'forId' must not be null"); this.forId = forId; } @@ -212,7 +213,8 @@ public class LabelTag extends AbstractHtmlElementTag { * Get the value of the '{@code id}' attribute. *

May be a runtime expression. */ - public String getFor() { + @Nullable + protected String getFor() { return this.forId; } @@ -239,6 +241,7 @@ public class LabelTag extends AbstractHtmlElementTag { * @return the value for the HTML '{@code name}' attribute */ @Override + @Nullable protected String getName() throws JspException { // This also suppresses the 'id' attribute (which is okay for a

May be a runtime expression. */ public void setLabel(String label) { - Assert.notNull(label, "'label' must not be null"); this.label = label; } /** * Get the text body of the rendered HTML {@code

If the {@link #setLabel label} property is set then the resolved value * of that property is used, otherwise the value of the {@code resolvedValue} * argument is used. */ @@ -388,6 +393,7 @@ public class OptionTag extends AbstractHtmlElementBodyTag implements BodyTag { return SelectedValueComparator.isSelected(getBindStatus(), resolvedValue); } + @Nullable private Object resolveValue() throws JspException { return evaluate(VALUE_VARIABLE_NAME, getValue()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionWriter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionWriter.java index 2a57fe73bd..352af46675 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionWriter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionWriter.java @@ -23,6 +23,7 @@ import javax.servlet.jsp.JspException; import org.springframework.beans.BeanWrapper; import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.servlet.support.BindStatus; @@ -92,15 +93,17 @@ class OptionWriter { private final BindStatus bindStatus; + @Nullable private final String valueProperty; + @Nullable private final String labelProperty; private final boolean htmlEscape; /** - * Creates a new {@code OptionWriter} for the supplied {@code objectSource}. + * Create a new {@code OptionWriter} for the supplied {@code objectSource}. * @param optionSource the source of the {@code options} (never {@code null}) * @param bindStatus the {@link BindStatus} for the bound value (never {@code null}) * @param valueProperty the name of the property used to render {@code option} values @@ -108,8 +111,8 @@ class OptionWriter { * @param labelProperty the name of the property used to render {@code option} labels * (optional) */ - public OptionWriter( - Object optionSource, BindStatus bindStatus, String valueProperty, String labelProperty, boolean htmlEscape) { + public OptionWriter(Object optionSource, BindStatus bindStatus, + @Nullable String valueProperty, @Nullable String labelProperty, boolean htmlEscape) { Assert.notNull(optionSource, "'optionSource' must not be null"); Assert.notNull(bindStatus, "'bindStatus' must not be null"); @@ -145,7 +148,7 @@ class OptionWriter { } /** - * Renders the inner '{@code option}' tags using the {@link #optionSource}. + * Render the inner '{@code option}' tags using the {@link #optionSource}. * @see #doRenderFromCollection(java.util.Collection, TagWriter) */ private void renderFromArray(TagWriter tagWriter) throws JspException { @@ -153,7 +156,7 @@ class OptionWriter { } /** - * Renders the inner '{@code option}' tags using the supplied + * Render the inner '{@code option}' tags using the supplied * {@link Map} as the source. * @see #renderOption(TagWriter, Object, Object, Object) */ @@ -173,7 +176,7 @@ class OptionWriter { } /** - * Renders the inner '{@code option}' tags using the {@link #optionSource}. + * Render the inner '{@code option}' tags using the {@link #optionSource}. * @see #doRenderFromCollection(java.util.Collection, TagWriter) */ private void renderFromCollection(TagWriter tagWriter) throws JspException { @@ -181,7 +184,7 @@ class OptionWriter { } /** - * Renders the inner '{@code option}' tags using the {@link #optionSource}. + * Render the inner '{@code option}' tags using the {@link #optionSource}. * @see #doRenderFromCollection(java.util.Collection, TagWriter) */ private void renderFromEnum(TagWriter tagWriter) throws JspException { @@ -189,7 +192,7 @@ class OptionWriter { } /** - * Renders the inner '{@code option}' tags using the supplied {@link Collection} of + * Render the inner '{@code option}' tags using the supplied {@link Collection} of * objects as the source. The value of the {@link #valueProperty} field is used * when rendering the '{@code value}' of the '{@code option}' and the value of the * {@link #labelProperty} property is used when rendering the label. @@ -213,10 +216,12 @@ class OptionWriter { } /** - * Renders an HTML '{@code option}' with the supplied value and label. Marks the + * Render an HTML '{@code option}' with the supplied value and label. Marks the * value as 'selected' if either the item itself or its value match the bound value. */ - private void renderOption(TagWriter tagWriter, Object item, Object value, Object label) throws JspException { + private void renderOption(TagWriter tagWriter, Object item, @Nullable Object value, @Nullable Object label) + throws JspException { + tagWriter.startTag("option"); writeCommonAttributes(tagWriter); @@ -239,17 +244,17 @@ class OptionWriter { } /** - * Determines the display value of the supplied {@code Object}, + * Determine the display value of the supplied {@code Object}, * HTML-escaped as required. */ - private String getDisplayString(Object value) { + private String getDisplayString(@Nullable Object value) { PropertyEditor editor = (value != null ? this.bindStatus.findEditor(value.getClass()) : null); return ValueFormatter.getDisplayString(value, editor, this.htmlEscape); } /** * Process the option value before it is written. - * The default implementation simply returns the same value unchanged. + *

The default implementation simply returns the same value unchanged. */ protected String processOptionValue(String resolvedValue) { return resolvedValue; @@ -257,9 +262,9 @@ class OptionWriter { /** * Determine whether the supplied values matched the selected value. - * Delegates to {@link SelectedValueComparator#isSelected}. + *

Delegates to {@link SelectedValueComparator#isSelected}. */ - private boolean isOptionSelected(Object resolvedValue) { + private boolean isOptionSelected(@Nullable Object resolvedValue) { return SelectedValueComparator.isSelected(this.bindStatus, resolvedValue); } @@ -271,7 +276,7 @@ class OptionWriter { } /** - * Writes default attributes configured to the supplied {@link TagWriter}. + * Write default attributes configured to the supplied {@link TagWriter}. */ protected void writeCommonAttributes(TagWriter tagWriter) throws JspException { } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionsTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionsTag.java index dc1908647b..3c728e0090 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionsTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionsTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -196,18 +197,21 @@ public class OptionsTag extends AbstractHtmlElementTag { * The {@link java.util.Collection}, {@link java.util.Map} or array of * objects used to generate the inner '{@code option}' tags. */ + @Nullable private Object items; /** * The name of the property mapped to the '{@code value}' attribute * of the '{@code option}' tag. */ + @Nullable private String itemValue; /** * The name of the property mapped to the inner text of the * '{@code option}' tag. */ + @Nullable private String itemLabel; private boolean disabled; @@ -229,6 +233,7 @@ public class OptionsTag extends AbstractHtmlElementTag { * of objects used to generate the inner '{@code option}' tags. *

Typically a runtime expression. */ + @Nullable protected Object getItems() { return this.items; } @@ -248,6 +253,7 @@ public class OptionsTag extends AbstractHtmlElementTag { * Return the name of the property mapped to the '{@code value}' * attribute of the '{@code option}' tag. */ + @Nullable protected String getItemValue() { return this.itemValue; } @@ -265,6 +271,7 @@ public class OptionsTag extends AbstractHtmlElementTag { * Get the name of the property mapped to the label (inner text) of the * '{@code option}' tag. */ + @Nullable protected String getItemLabel() { return this.itemLabel; } @@ -342,9 +349,12 @@ public class OptionsTag extends AbstractHtmlElementTag { */ private class OptionsWriter extends OptionWriter { + @Nullable private final String selectName; - public OptionsWriter(String selectName, Object optionSource, String valueProperty, String labelProperty) { + public OptionsWriter(@Nullable String selectName, Object optionSource, + @Nullable String valueProperty, @Nullable String labelProperty) { + super(optionSource, getBindStatus(), valueProperty, labelProperty, isHtmlEscape()); this.selectName = selectName; } @@ -364,7 +374,6 @@ public class OptionsTag extends AbstractHtmlElementTag { protected String processOptionValue(String value) { return processFieldValue(this.selectName, value, "option"); } - } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/RadioButtonTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/RadioButtonTag.java index 32696eb3b0..21525f4246 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/RadioButtonTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/RadioButtonTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectTag.java index e5de9dc595..5627e57165 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Map; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.servlet.support.BindStatus; @@ -255,39 +256,45 @@ public class SelectTag extends AbstractHtmlInputElementTag { /** - * The {@link Collection}, {@link Map} or array of objects used to generate the inner - * '{@code option}' tags. + * The {@link Collection}, {@link Map} or array of objects used to generate + * the inner '{@code option}' tags. */ + @Nullable private Object items; /** * The name of the property mapped to the '{@code value}' attribute * of the '{@code option}' tag. */ + @Nullable private String itemValue; /** * The name of the property mapped to the inner text of the * '{@code option}' tag. */ + @Nullable private String itemLabel; /** * The value of the HTML '{@code size}' attribute rendered * on the final '{@code select}' element. */ + @Nullable private String size; /** * Indicates whether or not the '{@code select}' tag allows * multiple-selections. */ + @Nullable private Object multiple; /** * The {@link TagWriter} instance that the output is being written. *

Only used in conjunction with nested {@link OptionTag OptionTags}. */ + @Nullable private TagWriter tagWriter; @@ -299,7 +306,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { *

Typically a runtime expression. * @param items the items that comprise the options of this selection */ - public void setItems(Object items) { + public void setItems(@Nullable Object items) { this.items = (items != null ? items : EMPTY); } @@ -307,6 +314,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { * Get the value of the '{@code items}' attribute. *

May be a runtime expression. */ + @Nullable protected Object getItems() { return this.items; } @@ -326,6 +334,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { * Get the value of the '{@code itemValue}' attribute. *

May be a runtime expression. */ + @Nullable protected String getItemValue() { return this.itemValue; } @@ -343,6 +352,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { * Get the value of the '{@code itemLabel}' attribute. *

May be a runtime expression. */ + @Nullable protected String getItemLabel() { return this.itemLabel; } @@ -358,6 +368,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { /** * Get the value of the '{@code size}' attribute. */ + @Nullable protected String getSize() { return this.size; } @@ -374,6 +385,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { * Get the value of the HTML '{@code multiple}' attribute rendered * on the final '{@code select}' element. */ + @Nullable protected Object getMultiple() { return this.multiple; } @@ -489,7 +501,7 @@ public class SelectTag extends AbstractHtmlInputElementTag { public int doEndTag() throws JspException { if (this.tagWriter != null) { this.tagWriter.endTag(); - writeHiddenTagIfNecessary(tagWriter); + writeHiddenTagIfNecessary(this.tagWriter); } return EVAL_PAGE; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectedValueComparator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectedValueComparator.java index fb73fb01c4..d43bf2c1a5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectedValueComparator.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/SelectedValueComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.servlet.support.BindStatus; @@ -59,11 +60,7 @@ abstract class SelectedValueComparator { * the supplied {@link BindStatus}. Equality in this case differs from standard Java equality and * is described in more detail here. */ - public static boolean isSelected(BindStatus bindStatus, Object candidateValue) { - if (bindStatus == null) { - return (candidateValue == null); - } - + public static boolean isSelected(BindStatus bindStatus, @Nullable Object candidateValue) { // Check obvious equality matches with the candidate first, // both with the rendered value and with the original value. Object boundValue = bindStatus.getValue(); @@ -85,14 +82,16 @@ abstract class SelectedValueComparator { // Non-null value but no obvious equality with the candidate value: // go into more exhaustive comparisons. boolean selected = false; - if (boundValue.getClass().isArray()) { - selected = collectionCompare(CollectionUtils.arrayToList(boundValue), candidateValue, bindStatus); - } - else if (boundValue instanceof Collection) { - selected = collectionCompare((Collection) boundValue, candidateValue, bindStatus); - } - else if (boundValue instanceof Map) { - selected = mapCompare((Map) boundValue, candidateValue, bindStatus); + if (candidateValue != null) { + if (boundValue.getClass().isArray()) { + selected = collectionCompare(CollectionUtils.arrayToList(boundValue), candidateValue, bindStatus); + } + else if (boundValue instanceof Collection) { + selected = collectionCompare((Collection) boundValue, candidateValue, bindStatus); + } + else if (boundValue instanceof Map) { + selected = mapCompare((Map) boundValue, candidateValue, bindStatus); + } } if (!selected) { selected = exhaustiveCompare(boundValue, candidateValue, bindStatus.getEditor(), null); @@ -100,8 +99,8 @@ abstract class SelectedValueComparator { return selected; } - private static boolean collectionCompare(Collection boundCollection, Object candidateValue, - BindStatus bindStatus) { + private static boolean collectionCompare( + Collection boundCollection, Object candidateValue, BindStatus bindStatus) { try { if (boundCollection.contains(candidateValue)) { return true; @@ -128,7 +127,7 @@ abstract class SelectedValueComparator { private static boolean exhaustiveCollectionCompare( Collection collection, Object candidateValue, BindStatus bindStatus) { - Map convertedValueCache = new HashMap<>(1); + Map convertedValueCache = new HashMap<>(); PropertyEditor editor = null; boolean candidateIsString = (candidateValue instanceof String); if (!candidateIsString) { @@ -145,8 +144,8 @@ abstract class SelectedValueComparator { return false; } - private static boolean exhaustiveCompare(Object boundValue, Object candidate, - PropertyEditor editor, Map convertedValueCache) { + private static boolean exhaustiveCompare(@Nullable Object boundValue, @Nullable Object candidate, + @Nullable PropertyEditor editor, @Nullable Map convertedValueCache) { String candidateDisplayString = ValueFormatter.getDisplayString(candidate, editor, false); if (boundValue != null && boundValue.getClass().isEnum()) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java index 9d17010c1e..1f15e1e4d9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.Writer; import java.util.ArrayDeque; import java.util.Deque; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; @@ -196,7 +195,7 @@ public class TagWriter { } private TagStateEntry currentState() { - return this.tagState.peek(); + return this.tagState.element(); } @@ -233,8 +232,10 @@ public class TagWriter { */ private static final class SafeWriter { + @Nullable private PageContext pageContext; + @Nullable private Writer writer; public SafeWriter(PageContext pageContext) { @@ -256,7 +257,9 @@ public class TagWriter { } private Writer getWriterToUse() { - return (this.pageContext != null ? this.pageContext.getOut() : this.writer); + Writer writer = (this.pageContext != null ? this.pageContext.getOut() : this.writer); + Assert.state(writer != null, "No Writer available"); + return writer; } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TextareaTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TextareaTag.java index f513950d8a..189b6bf853 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TextareaTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TextareaTag.java @@ -18,6 +18,8 @@ package org.springframework.web.servlet.tags.form; import javax.servlet.jsp.JspException; +import org.springframework.lang.Nullable; + /** * The {@code