spring OptionWriter 源码

  • 2022-08-08
  • 浏览 (336)

spring OptionWriter 代码

文件路径:/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/OptionWriter.java

/*
 * Copyright 2002-2012 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.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.tags.form;

import java.beans.PropertyEditor;
import java.util.Collection;
import java.util.Map;

import jakarta.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;

/**
 * Provides supporting functionality to render a list of '{@code option}'
 * tags based on some source object. This object can be either an array, a
 * {@link Collection}, or a {@link Map}.
 * <h3>Using an array or a {@link Collection}:</h3>
 * <p>
 * If you supply an array or {@link Collection} source object to render the
 * inner '{@code option}' tags, you may optionally specify the name of
 * the property on the objects which corresponds to the <em>value</em> of the
 * rendered '{@code option}' (i.e., the {@code valueProperty})
 * and the name of the property that corresponds to the <em>label</em> (i.e.,
 * the {@code labelProperty}). These properties are then used when
 * rendering each element of the array/{@link Collection} as an '{@code option}'.
 * If either property name is omitted, the value of {@link Object#toString()} of
 * the corresponding array/{@link Collection} element is used instead.  However,
 * if the item is an enum, {@link Enum#name()} is used as the default value.
 * </p>
 * <h3>Using a {@link Map}:</h3>
 * <p>
 * You can alternatively choose to render '{@code option}' tags by
 * supplying a {@link Map} as the source object.
 * </p>
 * <p>
 * If you <strong>omit</strong> property names for the <em>value</em> and
 * <em>label</em>:
 * </p>
 * <ul>
 * <li>the {@code key} of each {@link Map} entry will correspond to the
 * <em>value</em> of the rendered '{@code option}', and</li>
 * <li>the {@code value} of each {@link Map} entry will correspond to
 * the <em>label</em> of the rendered '{@code option}'.</li>
 * </ul>
 * <p>
 * If you <strong>supply</strong> property names for the <em>value</em> and
 * <em>label</em>:
 * </p>
 * <ul>
 * <li>the <em>value</em> of the rendered '{@code option}' will be
 * retrieved from the {@code valueProperty} on the object
 * corresponding to the {@code key} of each {@link Map} entry, and</li>
 * <li>the <em>label</em> of the rendered '{@code option}' will be
 * retrieved from the {@code labelProperty} on the object
 * corresponding to the {@code value} of each {@link Map} entry.
 * </ul>
 * <h3>When using either of these approaches:</h3>
 * <ul>
 * <li>Property names for the <em>value</em> and <em>label</em> are
 * specified as arguments to the
 * {@link #OptionWriter(Object, BindStatus, String, String, boolean) constructor}.</li>
 * <li>An '{@code option}' is marked as 'selected' if its key
 * {@link #isOptionSelected matches} the value that is bound to the tag instance.</li>
 * </ul>
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @author Scott Andrews
 * @since 2.0
 */
class OptionWriter {

	private final Object optionSource;

	private final BindStatus bindStatus;

	@Nullable
	private final String valueProperty;

	@Nullable
	private final String labelProperty;

	private final boolean htmlEscape;


	/**
	 * 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
	 * (optional)
	 * @param labelProperty the name of the property used to render {@code option} labels
	 * (optional)
	 */
	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");
		this.optionSource = optionSource;
		this.bindStatus = bindStatus;
		this.valueProperty = valueProperty;
		this.labelProperty = labelProperty;
		this.htmlEscape = htmlEscape;
	}


	/**
	 * Write the '{@code option}' tags for the configured {@link #optionSource} to
	 * the supplied {@link TagWriter}.
	 */
	public void writeOptions(TagWriter tagWriter) throws JspException {
		if (this.optionSource.getClass().isArray()) {
			renderFromArray(tagWriter);
		}
		else if (this.optionSource instanceof Collection) {
			renderFromCollection(tagWriter);
		}
		else if (this.optionSource instanceof Map) {
			renderFromMap(tagWriter);
		}
		else if (this.optionSource instanceof Class && ((Class<?>) this.optionSource).isEnum()) {
			renderFromEnum(tagWriter);
		}
		else {
			throw new JspException(
					"Type [" + this.optionSource.getClass().getName() + "] is not valid for option items");
		}
	}

	/**
	 * Render the inner '{@code option}' tags using the {@link #optionSource}.
	 * @see #doRenderFromCollection(java.util.Collection, TagWriter)
	 */
	private void renderFromArray(TagWriter tagWriter) throws JspException {
		doRenderFromCollection(CollectionUtils.arrayToList(this.optionSource), tagWriter);
	}

	/**
	 * Render the inner '{@code option}' tags using the supplied
	 * {@link Map} as the source.
	 * @see #renderOption(TagWriter, Object, Object, Object)
	 */
	private void renderFromMap(TagWriter tagWriter) throws JspException {
		Map<?, ?> optionMap = (Map<?, ?>) this.optionSource;
		for (Map.Entry<?, ?> entry : optionMap.entrySet()) {
			Object mapKey = entry.getKey();
			Object mapValue = entry.getValue();
			Object renderValue = (this.valueProperty != null ?
					PropertyAccessorFactory.forBeanPropertyAccess(mapKey).getPropertyValue(this.valueProperty) :
					mapKey);
			Object renderLabel = (this.labelProperty != null ?
					PropertyAccessorFactory.forBeanPropertyAccess(mapValue).getPropertyValue(this.labelProperty) :
					mapValue);
			renderOption(tagWriter, mapKey, renderValue, renderLabel);
		}
	}

	/**
	 * Render the inner '{@code option}' tags using the {@link #optionSource}.
	 * @see #doRenderFromCollection(java.util.Collection, TagWriter)
	 */
	private void renderFromCollection(TagWriter tagWriter) throws JspException {
		doRenderFromCollection((Collection<?>) this.optionSource, tagWriter);
	}

	/**
	 * Render the inner '{@code option}' tags using the {@link #optionSource}.
	 * @see #doRenderFromCollection(java.util.Collection, TagWriter)
	 */
	private void renderFromEnum(TagWriter tagWriter) throws JspException {
		doRenderFromCollection(CollectionUtils.arrayToList(((Class<?>) this.optionSource).getEnumConstants()), tagWriter);
	}

	/**
	 * 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.
	 */
	private void doRenderFromCollection(Collection<?> optionCollection, TagWriter tagWriter) throws JspException {
		for (Object item : optionCollection) {
			BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(item);
			Object value;
			if (this.valueProperty != null) {
				value = wrapper.getPropertyValue(this.valueProperty);
			}
			else if (item instanceof Enum) {
				value = ((Enum<?>) item).name();
			}
			else {
				value = item;
			}
			Object label = (this.labelProperty != null ? wrapper.getPropertyValue(this.labelProperty) : item);
			renderOption(tagWriter, item, value, label);
		}
	}

	/**
	 * 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, @Nullable Object value, @Nullable Object label)
			throws JspException {

		tagWriter.startTag("option");
		writeCommonAttributes(tagWriter);

		String valueDisplayString = getDisplayString(value);
		String labelDisplayString = getDisplayString(label);

		valueDisplayString = processOptionValue(valueDisplayString);

		// allows render values to handle some strange browser compat issues.
		tagWriter.writeAttribute("value", valueDisplayString);

		if (isOptionSelected(value) || (value != item && isOptionSelected(item))) {
			tagWriter.writeAttribute("selected", "selected");
		}
		if (isOptionDisabled()) {
			tagWriter.writeAttribute("disabled", "disabled");
		}
		tagWriter.appendValue(labelDisplayString);
		tagWriter.endTag();
	}

	/**
	 * Determine the display value of the supplied {@code Object},
	 * HTML-escaped as required.
	 */
	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.
	 * <p>The default implementation simply returns the same value unchanged.
	 */
	protected String processOptionValue(String resolvedValue) {
		return resolvedValue;
	}

	/**
	 * Determine whether the supplied values matched the selected value.
	 * <p>Delegates to {@link SelectedValueComparator#isSelected}.
	 */
	private boolean isOptionSelected(@Nullable Object resolvedValue) {
		return SelectedValueComparator.isSelected(this.bindStatus, resolvedValue);
	}

	/**
	 * Determine whether the option fields should be disabled.
	 */
	protected boolean isOptionDisabled() throws JspException {
		return false;
	}

	/**
	 * Write default attributes configured to the supplied {@link TagWriter}.
	 */
	protected void writeCommonAttributes(TagWriter tagWriter) throws JspException {
	}

}

相关信息

spring 源码目录

相关文章

spring AbstractCheckedElementTag 源码

spring AbstractDataBoundFormElementTag 源码

spring AbstractFormTag 源码

spring AbstractHtmlElementBodyTag 源码

spring AbstractHtmlElementTag 源码

spring AbstractHtmlInputElementTag 源码

spring AbstractMultiCheckedElementTag 源码

spring AbstractSingleCheckedElementTag 源码

spring ButtonTag 源码

spring CheckboxTag 源码

0  赞