/* Copyright 2005-2006 Tim Fennell
*
* 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 net.sourceforge.stripes.tag;
import net.sourceforge.stripes.exception.StripesJspException;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.util.HtmlUtil;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.DynamicAttributes;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Provides basic facilities for any tag that wishes to mimic a standard HTML/XHTML tag. Includes
* getters and setters for all basic HTML attributes and JavaScript event attributes. Also includes
* several of the support methods from the Tag interface, but does not directly or indirectly
* implement either Tag or BodyTag.
*
* @author Tim Fennell
*/
public abstract class HtmlTagSupport extends StripesTagSupport implements DynamicAttributes {
/** Log implementation used to log errors during tag writing. */
private static final Log log = Log.getInstance(HtmlTagSupport.class);
/** Map containing all attributes of the tag. */
private final Map<String,String> attributes = new HashMap<String,String>();
/** Storage for a BodyContent instance, should the eventual child class implement BodyTag. */
private BodyContent bodyContent;
/** Sets the named attribute to the supplied value. */
protected final void set(String name, String value) {
if (value == null) {
this.attributes.remove(name);
}
else {
this.attributes.put(name, value);
}
}
/** Gets the value of the named attribute, or null if it is not set. */
protected final String get(String name) {
return this.attributes.get(name);
}
/** Gets the map containing the attributes of the tag. */
protected final Map<String,String> getAttributes() {
return this.attributes;
}
/**
* Accepts any dynamic attributes that are supplied to the tag and stored them
* in the map of attributes that get written back to the page.
*
* @param uri the URI of the namespace of the attribute if it has one. Totally ignored!
* @param name the name of the attribute
* @param value the value of the attribute
* @throws JspException not thrown from this class; included so that subclasses can
* override the method and throw the interface exception
*/
public void setDynamicAttribute(String uri, String name, Object value) throws JspException {
set(name, value == null ? "" : value.toString());
}
/** Returns the BodyContent of the tag if one has been provided by the JSP container. */
public BodyContent getBodyContent() {
return bodyContent;
}
/** Called by the JSP container to set the BodyContent on the tag. */
public void setBodyContent(BodyContent bodyContent) {
this.bodyContent = bodyContent;
}
/** Release method to clean up the state of the tag ready for re-use. */
@Override
public void release() {
this.pageContext = null;
this.parentTag = null;
this.bodyContent = null;
this.attributes.clear();
}
/**
* Checks to see if there is a body content for this tag, and if its value is non-null
* and non-zero-length. If so, returns it as a String, otherwise returns null.
* @return String the value of the body if one was set
*/
protected String getBodyContentAsString() {
String returnValue = null;
if (this.bodyContent != null) {
String body = getBodyContent().getString();
if (body != null && body.length() > 0) {
returnValue = body;
}
}
return returnValue;
}
/**
* Returns true if HTML tags that have no body should be closed like XML tags, with "/>".
* False if such HTML tags should be closed in the style of HTML4, with just a ">".
*
* @see PageOptionsTag#setHtmlMode(String)
*/
protected boolean isXmlTags() {
return !"html".equalsIgnoreCase(PageOptionsTag.getHtmlMode(pageContext));
}
/**
* Writes out an opening tag. Uses the parameter "tag" to determine the name of the open tag
* and then uses the map of attributes assembled through various setter calls to fill in the
* tag attributes.
*
* @param writer the JspWriter to write the open tag to
* @param tag the name of the tag to use
* @throws JspException if the JspWriter causes an exception
*/
protected void writeOpenTag(JspWriter writer, String tag) throws JspException {
try {
writer.print("<");
writer.print(tag);
writeAttributes(writer);
writer.print(">");
}
catch (IOException ioe) {
JspException jspe = new JspException("IOException encountered while writing open tag <" +
tag + "> to the JspWriter.", ioe);
log.warn(jspe);
throw jspe;
}
}
/**
* Writes out a close tag using the tag name supplied.
*
* @param writer the JspWriter to write the open tag to
* @param tag the name of the tag to use
* @throws JspException if the JspWriter causes an exception
*/
protected void writeCloseTag(JspWriter writer, String tag) throws JspException {
try {
writer.print("</");
writer.print(tag);
writer.print(">");
}
catch (IOException ioe) {
JspException jspe = new JspException("IOException encountered while writing close tag </" +
tag + "> to the JspWriter.", ioe);
log.warn(jspe);
throw jspe;
}
}
/**
* Writes out a singleton tag (aka a bodiless tag or self-closing tag). Similar to
* writeOpenTag except that instead of leaving the tag open, it closes the tag.
*
* @param writer the JspWriter to write the open tag to
* @param tag the name of the tag to use
* @throws JspException if the JspWriter causes an exception
*/
protected void writeSingletonTag(JspWriter writer, String tag) throws JspException{
try {
writer.print("<");
writer.print(tag);
writeAttributes(writer);
writer.print(isXmlTags() ? " />" : ">");
}
catch (IOException ioe) {
JspException jspe = new JspException("IOException encountered while writing singleton tag <" +
tag + "/> to the JspWriter.", ioe);
log.warn(jspe);
throw jspe;
}
}
/**
* For every attribute stored in the attributes map for this tag, writes out the tag
* attributes in the form x="y". All attributes are HTML encoded before being written
* to the page to ensure that HTML special characters are rendered properly.
*
* @param writer the JspWriter to write the open tag to
* @throws IOException if the JspWriter causes an exception
*/
protected void writeAttributes(JspWriter writer) throws IOException {
for (Map.Entry<String,String> attr: getAttributes().entrySet() ) {
// Skip the output of blank attributes!
String value = attr.getValue();
if (value == null) continue;
writer.print(" ");
writer.print(attr.getKey());
writer.print("=\"");
writer.print( HtmlUtil.encode(value) );
writer.print("\"");
}
}
/**
* Evaluates a single expression and returns the result. If the expression cannot be evaluated
* then an ELException is caught, wrapped in a JspException and re-thrown.
*
* @param expression the expression to be evaluated
* @param resultType the Class representing the desired return type from the expression
* @throws StripesJspException when an ELException occurs trying to evaluate the expression
*/
@SuppressWarnings({ "unchecked", "deprecation" })
protected <R> R evaluateExpression(String expression, Class<R> resultType) throws StripesJspException {
try {
return (R) this.pageContext.getExpressionEvaluator().
evaluate(expression, resultType, this.pageContext.getVariableResolver(), null);
}
catch (javax.servlet.jsp.el.ELException ele) {
throw new StripesJspException
("Could not evaluate EL expression [" + expression + "] with result type [" +
resultType.getName() + "] in tag class of type: " + getClass().getName(), ele);
}
}
/**
* Returns a String representation of the class, including the map of attributes that
* are set on the tag, the toString of its parent tag, and the pageContext.
*/
@Override
public String toString() {
return getClass().getSimpleName()+ "{" +
"attributes=" + attributes +
", parentTag=" + parentTag +
", pageContext=" + pageContext +
"}";
}
public void setId(String id) { set("id", id); }
public String getId() { return get("id"); }
public void setClass(String cssClass) { set("class", cssClass); }
public void setCssClass(String cssClass) { set("class", cssClass); }
public String getCssClass() { return get("class"); }
public void setTitle(String title) { set("title", title); }
public String getTitle() { return get("title"); }
public void setStyle(String style) { set("style", style); }
public String getStyle() { return get("style"); }
public void setDir(String dir) { set("dir", dir); }
public String getDir() { return get("dir"); }
public void setLang(String lang) { set("lang", lang); }
public String getLang() { return get("lang"); }
public void setTabindex(String tabindex) { set("tabindex", tabindex); }
public String getTabindex() { return get("tabindex"); }
public void setAccesskey(String accesskey) { set("accesskey", accesskey); }
public String getAccesskey() { return get("accesskey"); }
public void setOnfocus(String onfocus) { set("onfocus", onfocus); }
public String getOnfocus() { return get("onfocus"); }
public void setOnblur(String onblur) { set("onblur", onblur); }
public String getOnblur() { return get("onblur"); }
public void setOnselect(String onselect) { set("onselect", onselect); }
public String getOnselect() { return get("onselect"); }
public void setOnchange(String onchange) { set("onchange", onchange); }
public String getOnchange() { return get("onchange"); }
public void setOnclick(String onclick) { set("onclick", onclick); }
public String getOnclick() { return get("onclick"); }
public void setOndblclick(String ondblclick) { set("ondblclick", ondblclick); }
public String getOndblclick() { return get("ondblclick"); }
public void setOnmousedown(String onmousedown) { set("onmousedown", onmousedown); }
public String getOnmousedown() { return get("onmousedown"); }
public void setOnmouseup(String onmouseup) { set("onmouseup", onmouseup); }
public String getOnmouseup() { return get("onmouseup"); }
public void setOnmouseover(String onmouseover) { set("onmouseover", onmouseover); }
public String getOnmouseover() { return get("onmouseover"); }
public void setOnmousemove(String onmousemove) { set("onmousemove", onmousemove); }
public String getOnmousemove() { return get("onmousemove"); }
public void setOnmouseout(String onmouseout) { set("onmouseout", onmouseout); }
public String getOnmouseout() { return get("onmouseout"); }
public void setOnkeypress(String onkeypress) { set("onkeypress", onkeypress); }
public String getOnkeypress() { return get("onkeypress"); }
public void setOnkeydown(String onkeydown) { set("onkeydown", onkeydown); }
public String getOnkeydown() { return get("onkeydown"); }
public void setOnkeyup(String onkeyup) { set("onkeyup", onkeyup); }
public String getOnkeyup() { return get("onkeyup"); }
}