/*
 * 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.
 * You may obtain a copy of the License at
 *
 *      http://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.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.Deque;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

Utility class for writing HTML content to a Writer instance.

Intended to support output from JSP tag libraries.

Author:Rob Harrop, Juergen Hoeller
Since:2.0
/** * Utility class for writing HTML content to a {@link Writer} instance. * * <p>Intended to support output from JSP tag libraries. * * @author Rob Harrop * @author Juergen Hoeller * @since 2.0 */
public class TagWriter {
The SafeWriter to write to.
/** * The {@link SafeWriter} to write to. */
private final SafeWriter writer;
Stores tag state. Stack model naturally supports tag nesting.
/** * Stores {@link TagStateEntry tag state}. Stack model naturally supports tag nesting. */
private final Deque<TagStateEntry> tagState = new ArrayDeque<>();
Create a new instance of the TagWriter class that writes to the supplied PageContext.
Params:
  • pageContext – the JSP PageContext to obtain the Writer from
/** * Create a new instance of the {@link TagWriter} class that writes to * the supplied {@link PageContext}. * @param pageContext the JSP PageContext to obtain the {@link Writer} from */
public TagWriter(PageContext pageContext) { Assert.notNull(pageContext, "PageContext must not be null"); this.writer = new SafeWriter(pageContext); }
Create a new instance of the TagWriter class that writes to the supplied Writer.
Params:
  • writer – the Writer to write tag content to
/** * Create a new instance of the {@link TagWriter} class that writes to * the supplied {@link Writer}. * @param writer the {@link Writer} to write tag content to */
public TagWriter(Writer writer) { Assert.notNull(writer, "Writer must not be null"); this.writer = new SafeWriter(writer); }
Start a new tag with the supplied name. Leaves the tag open so that attributes, inner text or nested tags can be written into it.
See Also:
  • endTag()
/** * Start a new tag with the supplied name. Leaves the tag open so * that attributes, inner text or nested tags can be written into it. * @see #endTag() */
public void startTag(String tagName) throws JspException { if (inTag()) { closeTagAndMarkAsBlock(); } push(tagName); this.writer.append("<").append(tagName); }
Write an HTML attribute with the specified name and value.

Be sure to write all attributes before writing any inner text or nested tags.

Throws:
  • IllegalStateException – if the opening tag is closed
/** * Write an HTML attribute with the specified name and value. * <p>Be sure to write all attributes <strong>before</strong> writing * any inner text or nested tags. * @throws IllegalStateException if the opening tag is closed */
public void writeAttribute(String attributeName, String attributeValue) throws JspException { if (currentState().isBlockTag()) { throw new IllegalStateException("Cannot write attributes after opening tag is closed."); } this.writer.append(" ").append(attributeName).append("=\"") .append(attributeValue).append("\""); }
Write an HTML attribute if the supplied value is not null or zero length.
See Also:
/** * Write an HTML attribute if the supplied value is not {@code null} * or zero length. * @see #writeAttribute(String, String) */
public void writeOptionalAttributeValue(String attributeName, @Nullable String attributeValue) throws JspException { if (StringUtils.hasText(attributeValue)) { writeAttribute(attributeName, attributeValue); } }
Close the current opening tag (if necessary) and appends the supplied value as inner text.
Throws:
  • IllegalStateException – if no tag is open
/** * Close the current opening tag (if necessary) and appends the * supplied value as inner text. * @throws IllegalStateException if no tag is open */
public void appendValue(String value) throws JspException { if (!inTag()) { throw new IllegalStateException("Cannot write tag value. No open tag available."); } closeTagAndMarkAsBlock(); this.writer.append(value); }
Indicate that the currently open tag should be closed and marked as a block level element.

Useful when you plan to write additional content in the body outside the context of the current TagWriter.

/** * Indicate that the currently open tag should be closed and marked * as a block level element. * <p>Useful when you plan to write additional content in the body * outside the context of the current {@link TagWriter}. */
public void forceBlock() throws JspException { if (currentState().isBlockTag()) { return; // just ignore since we are already in the block } closeTagAndMarkAsBlock(); }
Close the current tag.

Correctly writes an empty tag if no inner text or nested tags have been written.

/** * Close the current tag. * <p>Correctly writes an empty tag if no inner text or nested tags * have been written. */
public void endTag() throws JspException { endTag(false); }
Close the current tag, allowing to enforce a full closing tag.

Correctly writes an empty tag if no inner text or nested tags have been written.

Params:
  • enforceClosingTag – whether a full closing tag should be rendered in any case, even in case of a non-block tag
/** * Close the current tag, allowing to enforce a full closing tag. * <p>Correctly writes an empty tag if no inner text or nested tags * have been written. * @param enforceClosingTag whether a full closing tag should be * rendered in any case, even in case of a non-block tag */
public void endTag(boolean enforceClosingTag) throws JspException { if (!inTag()) { throw new IllegalStateException("Cannot write end of tag. No open tag available."); } boolean renderClosingTag = true; if (!currentState().isBlockTag()) { // Opening tag still needs to be closed... if (enforceClosingTag) { this.writer.append(">"); } else { this.writer.append("/>"); renderClosingTag = false; } } if (renderClosingTag) { this.writer.append("</").append(currentState().getTagName()).append(">"); } this.tagState.pop(); }
Adds the supplied tag name to the tag state.
/** * Adds the supplied tag name to the {@link #tagState tag state}. */
private void push(String tagName) { this.tagState.push(new TagStateEntry(tagName)); }
Closes the current opening tag and marks it as a block tag.
/** * Closes the current opening tag and marks it as a block tag. */
private void closeTagAndMarkAsBlock() throws JspException { if (!currentState().isBlockTag()) { currentState().markAsBlockTag(); this.writer.append(">"); } } private boolean inTag() { return !this.tagState.isEmpty(); } private TagStateEntry currentState() { return this.tagState.element(); }
Holds state about a tag and its rendered behavior.
/** * Holds state about a tag and its rendered behavior. */
private static class TagStateEntry { private final String tagName; private boolean blockTag; public TagStateEntry(String tagName) { this.tagName = tagName; } public String getTagName() { return this.tagName; } public void markAsBlockTag() { this.blockTag = true; } public boolean isBlockTag() { return this.blockTag; } }
Simple Writer wrapper that wraps all IOExceptions in JspExceptions.
/** * Simple {@link Writer} wrapper that wraps all * {@link IOException IOExceptions} in {@link JspException JspExceptions}. */
private static final class SafeWriter { @Nullable private PageContext pageContext; @Nullable private Writer writer; public SafeWriter(PageContext pageContext) { this.pageContext = pageContext; } public SafeWriter(Writer writer) { this.writer = writer; } public SafeWriter append(String value) throws JspException { try { getWriterToUse().write(String.valueOf(value)); return this; } catch (IOException ex) { throw new JspException("Unable to write to JspWriter", ex); } } private Writer getWriterToUse() { Writer writer = (this.pageContext != null ? this.pageContext.getOut() : this.writer); Assert.state(writer != null, "No Writer available"); return writer; } } }