blob: a42e0a10bc73889ef340193a146f3580ddef7343 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.click.util;
import java.util.Map;
/**
* Provides a HTML element StringBuffer for rendering, automatically
* escaping string values. HtmlStringBuffer is used by Click controls
* for HTML rendering.
* <p/>
* For example the following code:
* <pre class="codeJava">
* <span class="kw">public</span> String toString() {
* HtmlStringBuffer buffer = <span class="kw">new</span> HtmlStringBuffer();
*
* buffer.elementStart(<span class="st">"input"</span>);
* buffer.appendAttribute(<span class="st">"type"</span>, <span class="st">"text"</span>);
* buffer.appendAttribute(<span class="st">"name"</span>, getName());
* buffer.appendAttribute(<span class="st">"value"</span>, getValue());
* buffer.elementEnd();
*
* <span class="kw">return</span> buffer.toString();
* } </pre>
*
* Would render:
*
* <pre class="codeHtml">
* &lt;input type="text" name="address" value="23 Holt's Street"/&gt; </pre>
*
* <h4>Synchronization</h4>
*
* To improve performance in Click's thread safe environment this
* class does not synchronize append operations. Internally this class uses
* a character buffer adapted from the JDK 1.5 <tt>AbstractStringBuilder</tt>.
*/
public class HtmlStringBuffer {
// -------------------------------------------------------------- Constants
/** JavaScript attribute names. */
static final String[] JS_ATTRIBUTES = {
"onload", "onunload", "onclick", "ondblclick", "onmousedown",
"onmouseup", "onmouseover", "onmousemove", "onmouseout", "onfocus",
"onblur", "onkeypress", "onkeydown", "onkeyup", "onsubmit", "onreset",
"onselect", "onchange"
};
// ----------------------------------------------------- Instance Variables
/** The character storage array. */
protected char[] characters;
/** The count is the number of characters used. */
protected int count;
// ----------------------------------------------------------- Constructors
/**
* Create a new HTML StringBuffer with the specified initial
* capacity.
*
* @param length the initial capacity
*/
public HtmlStringBuffer(int length) {
characters = new char[length];
}
/**
* Create a new HTML StringBuffer with an initial capacity of 128
* characters.
*/
public HtmlStringBuffer() {
characters = new char[128];
}
// --------------------------------------------------------- Public Methods
/**
* Append the double value to the buffer.
*
* @param value the double value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer append(double value) {
append(String.valueOf(value));
return this;
}
/**
* Append the char value to the buffer.
*
* @param value the char value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer append(char value) {
int newcount = count + 1;
if (newcount > characters.length) {
expandCapacity(newcount);
}
characters[count++] = value;
return this;
}
/**
* Append the integer value to the buffer.
*
* @param value the integer value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer append(int value) {
append(String.valueOf(value));
return this;
}
/**
* Append the long value to the buffer.
*
* @param value the long value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer append(long value) {
append(String.valueOf(value));
return this;
}
/**
* Append the raw object value of the given object to the buffer.
*
* @param value the object value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer append(Object value) {
String string = String.valueOf(value);
int length = string.length();
int newCount = count + length;
if (newCount > characters.length) {
expandCapacity(newCount);
}
string.getChars(0, length, characters, count);
count = newCount;
return this;
}
/**
* Append the raw string value of the given object to the buffer.
*
* @param value the string value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer append(String value) {
String string = (value != null) ? value : "null";
int length = string.length();
int newCount = count + length;
if (newCount > characters.length) {
expandCapacity(newCount);
}
string.getChars(0, length, characters, count);
count = newCount;
return this;
}
/**
* Append the given value to the buffer and escape its value. The following
* characters are escaped: &lt;, &gt;, &quot;, &#039;, &amp;.
*
* @param value the object value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if the value is null
*/
public HtmlStringBuffer appendEscaped(Object value) {
if (value == null) {
throw new IllegalArgumentException("Null value parameter");
}
String string = value.toString();
boolean requiresEscape = false;
for (int i = 0, size = string.length(); i < size; i++) {
if (ClickUtils.requiresEscape(string.charAt(i))) {
requiresEscape = true;
break;
}
}
if (requiresEscape) {
ClickUtils.appendEscapeString(string, this);
} else {
append(string);
}
return this;
}
/**
* Append the given HTML attribute name and value to the string buffer, and
* do not escape the attribute value.
* <p/>
* For example:
* <pre class="javaCode">
* appendAttribute(<span class="st">"size"</span>, 10) <span class="green">-&gt;</span> <span class="st">size="10"</span> </pre>
*
* @param name the HTML attribute name
* @param value the HTML attribute value
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if name is null
*/
public HtmlStringBuffer appendAttribute(String name, Object value) {
if (name == null) {
throw new IllegalArgumentException("Null name parameter");
}
if (value != null) {
append(" ");
append(name);
append("=\"");
append(value);
append("\"");
}
return this;
}
/**
* Append the given attribute name and value to the buffer, if the value
* is not null.
* <p/>
* For example:
* <pre class="javaCode">
* appendAttribute(<span class="st">"class"</span>, <span class="st">"required"</span>) <span class="green">-></span> <span class="st">class="required"</span> </pre>
*
* The attribute value will be escaped. The following characters are escaped:
* &lt;, &gt;, &quot;, &#039;, &amp;.
* <p/>
* If the attribute name is a JavaScript event handler the value will
* not be escaped.
*
* @param name the HTML attribute name
* @param value the object value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if name is null
*/
public HtmlStringBuffer appendAttributeEscaped(String name, Object value) {
if (name == null) {
throw new IllegalArgumentException("Null name parameter");
}
if (value != null) {
append(" ");
append(name);
append("=\"");
if (isJavaScriptAttribute(name)) {
append(value);
} else {
appendEscaped(value.toString());
}
append("\"");
}
return this;
}
/**
* Append the given HTML attribute name and value to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* appendAttribute(<span class="st">"size"</span>, 10) <span class="green">-&gt;</span> <span class="st">size="10"</span> </pre>
*
* @param name the HTML attribute name
* @param value the HTML attribute value
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if name is null
*/
public HtmlStringBuffer appendAttribute(String name, int value) {
if (name == null) {
throw new IllegalArgumentException("Null name parameter");
}
append(" ");
append(name);
append("=\"");
append(value);
append("\"");
return this;
}
/**
* Append the HTML "disabled" attribute to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* appendAttributeDisabled() <span class="green">-></span> <span class="st">disabled="disabled"</span> </pre>
*
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer appendAttributeDisabled() {
append(" disabled=\"disabled\"");
return this;
}
/**
* Append the HTML "readonly" attribute to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* appendAttributeReadonly() <span class="green">-></span> <span class="st">readonly="readonly"</span> </pre>
*
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer appendAttributeReadonly() {
append(" readonly=\"readonly\"");
return this;
}
/**
* Append the given map of attribute names and values to the string buffer.
*
* @param attributes the map of attribute names and values
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if attributes is null
*/
public HtmlStringBuffer appendAttributes(Map<String, String> attributes) {
if (attributes == null) {
throw new IllegalArgumentException("Null attributes parameter");
}
for (Map.Entry<String, String> entry : attributes.entrySet()) {
String name = entry.getKey();
if (!name.equals("id")) {
appendAttributeEscaped(name, entry.getValue());
}
}
return this;
}
/**
* Append the given map of CSS style name and value pairs as a style
* attribute to the string buffer.
*
* @param attributes the map of CSS style names and values
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if attributes is null
*/
public HtmlStringBuffer appendStyleAttributes(Map<String, String> attributes) {
if (attributes == null) {
throw new IllegalArgumentException("Null attributes parameter");
}
if (!attributes.isEmpty()) {
append(" style=\"");
for (Map.Entry<String, String> entry : attributes.entrySet()) {
append(entry.getKey());
append(":");
append(entry.getValue());
append(";");
}
append("\"");
}
return this;
}
/**
* Append a HTML element end to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* elementEnd(<span class="st">"textarea"</span>) <span class="green">-></span> <span class="st">&lt;/textarea&gt;</span> </pre>
*
* @param name the HTML element name to end
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if name is null
*/
public HtmlStringBuffer elementEnd(String name) {
if (name == null) {
throw new IllegalArgumentException("Null name parameter");
}
append("</");
append(name);
append(">");
return this;
}
/**
* Append a HTML element end to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* closeTag() <span class="green">-></span> <span class="st">&gt;</span> </pre>
*
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer closeTag() {
append(">");
return this;
}
/**
* Append a HTML element end to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* elementEnd() <span class="green">-></span> <span class="st">/&gt;</span> </pre>
*
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer elementEnd() {
append("/>");
return this;
}
/**
* Append a HTML element start to the string buffer.
* <p/>
* For example:
* <pre class="javaCode">
* elementStart(<span class="st">"input"</span>) <span class="green">-></span> <span class="st">&lt;input</span> </pre>
*
* @param name the HTML element name to start
* @return a reference to this <tt>HtmlStringBuffer</tt> object
*/
public HtmlStringBuffer elementStart(String name) {
append("<");
append(name);
return this;
}
/**
* Return true if the given attribute name is a JavaScript attribute,
* or false otherwise.
*
* @param name the HTML attribute name to test
* @return true if the HTML attribute is a JavaScript attribute
*/
public boolean isJavaScriptAttribute(String name) {
if (name.length() < 6 || name.length() > 11) {
return false;
}
if (!name.startsWith("on")) {
return false;
}
for (String jsAttribute : JS_ATTRIBUTES) {
if (jsAttribute.equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
/**
* Return the length of the string buffer.
*
* @return the length of the string buffer
*/
public int length() {
return count;
}
/**
* @see Object#toString()
*
* @return a string representation of the string buffer
*/
@Override
public String toString() {
return new String(characters, 0, count);
}
// ------------------------------------------------------ Protected Methods
/**
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
* array is allocated with greater capacity. The new capacity is the
* larger of:
* <ul>
* <li>The <code>minimumCapacity</code> argument.
* <li>Twice the old capacity, plus <code>2</code>.
* </ul>
* If the <code>minimumCapacity</code> argument is non-positive, this method
* takes no action and simply returns.
*
* @param minimumCapacity the minimum desired capacity
*/
protected void expandCapacity(int minimumCapacity) {
int newCapacity = (characters.length + 1) * 2;
if (newCapacity < 0) {
newCapacity = Integer.MAX_VALUE;
} else if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(characters, 0, newValue, 0, count);
characters = newValue;
}
// Private Package Methods ------------------------------------------------
/**
* Append the given value to the buffer and HTML escape its value.
*
* @param value the object value to append
* @return a reference to this <tt>HtmlStringBuffer</tt> object
* @throws IllegalArgumentException if the value is null
*/
HtmlStringBuffer appendHtmlEscaped(Object value) {
if (value == null) {
throw new IllegalArgumentException("Null value parameter");
}
String string = value.toString();
boolean requiresEscape = false;
for (int i = 0, size = string.length(); i < size; i++) {
if (ClickUtils.requiresHtmlEscape(string.charAt(i))) {
requiresEscape = true;
break;
}
}
if (requiresEscape) {
ClickUtils.appendHtmlEscapeString(string, this);
} else {
append(value);
}
return this;
}
}