blob: 499bc44a6a5d3f043ec7997b50426aee6b8872ff [file] [log] [blame]
// Copyright 2005 The Apache Software Foundation
//
// 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.apache.tapestry.markup;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.util.Defense;
import org.apache.tapestry.IMarkupWriter;
import org.apache.tapestry.NestedMarkupWriter;
import java.io.PrintWriter;
import java.util.*;
/**
* Completely revised (for 4.0) implementation of {@link org.apache.tapestry.IMarkupWriter}. No
* longer does internal buffering (since the servlet/portlet APIs support that natively) and wraps
* around a {@link java.io.PrintWriter} (rather than an {@link java.io.OutputStream}).
*
* @author Howard M. Lewis Ship
* @since 4.0
*/
public class MarkupWriterImpl implements IMarkupWriter
{
/**
* The underlying {@link PrintWriter}that output is sent to.
*/
private PrintWriter _writer;
/**
* Filter used to "escape" characters that need any kind of special encoding for the output
* content type.
*/
private MarkupFilter _filter;
/**
* Indicates whether a tag is open or not. A tag is opened by {@link #begin(String)}or
* {@link #beginEmpty(String)}. It stays open while calls to the <code>attribute()</code>
* methods are made. It is closed (the '&gt;' is written) when any other method is invoked.
*/
private boolean _openTag = false;
/**
* Indicates that the tag was opened with {@link #beginEmpty(String)}, which affects how the
* tag is closed (a slash is added to indicate the lack of a body). This is compatible with
* HTML, but reflects an XML/XHTML leaning.
*/
private boolean _emptyTag = false;
private String _contentType;
/**
* A Stack of Strings used to track the active tag elements. Elements are active until the
* corresponding close tag is written. The {@link #push(String)}method adds elements to the
* stack, {@link #pop()}removes them.
*/
private List _activeElementStack;
/**
* Attributes are stored in a map until an open tag is closed. The linked hashmap ensures that
* ordering remains constant.
*/
private final Map _attrMap = new LinkedHashMap();
public MarkupWriterImpl(String contentType, PrintWriter writer, MarkupFilter filter)
{
Defense.notNull(contentType, "contentType");
Defense.notNull(writer, "writer");
Defense.notNull(filter, "filter");
_contentType = contentType;
_writer = writer;
_filter = filter;
}
public void attribute(String name, int value)
{
checkTagOpen();
_attrMap.put(name, new DefaultAttribute(String.valueOf(value), false));
}
public void attribute(String name, boolean value)
{
checkTagOpen();
_attrMap.put(name, new DefaultAttribute(String.valueOf(value), false));
}
public void attribute(String name, String value)
{
attribute(name, value, false);
}
public void attribute(String name, String value, boolean raw)
{
checkTagOpen();
_attrMap.put(name, new DefaultAttribute(value, raw));
}
public void appendAttribute(String name, boolean value)
{
checkTagOpen();
appendAttribute(name, String.valueOf(value));
}
public void appendAttribute(String name, int value)
{
checkTagOpen();
appendAttribute(name, String.valueOf(value));
}
public void appendAttribute(String name, String value)
{
checkTagOpen();
DefaultAttribute attr = (DefaultAttribute)_attrMap.get(name);
if (attr == null) {
attr = new DefaultAttribute(value, false);
_attrMap.put(name, attr);
return;
}
attr.append(value);
}
public void appendAttributeRaw(String name, String value)
{
checkTagOpen();
DefaultAttribute attr = (DefaultAttribute)_attrMap.get(name);
if (attr == null) {
attr = new DefaultAttribute(value, true);
_attrMap.put(name, attr);
return;
}
attr.setRaw(true);
attr.append(value);
}
public Attribute getAttribute(String name)
{
checkTagOpen();
return (Attribute)_attrMap.get(name);
}
public boolean hasAttribute(String name)
{
checkTagOpen();
return _attrMap.containsKey(name);
}
public void clearAttributes()
{
checkTagOpen();
_attrMap.clear();
}
public Attribute removeAttribute(String name)
{
checkTagOpen();
return (Attribute)_attrMap.remove(name);
}
/**
* Prints the value, if non-null. May pass it through the filter, unless raw is true.
*/
private void maybePrintFiltered(char[] data, int offset, int length, boolean raw, boolean isAttribute)
{
if (data == null || length <= 0)
return;
if (raw)
{
_writer.write(data, offset, length);
return;
}
_filter.print(_writer, data, offset, length, isAttribute);
}
public void attributeRaw(String name, String value)
{
attribute(name, value, true);
}
public void begin(String name)
{
if (_openTag)
closeTag();
push(name);
_writer.print('<');
_writer.print(name);
_openTag = true;
_emptyTag = false;
}
public void beginEmpty(String name)
{
if (_openTag)
closeTag();
_writer.print('<');
_writer.print(name);
_openTag = true;
_emptyTag = true;
}
public boolean checkError()
{
return _writer.checkError();
}
public void close()
{
if (_openTag)
closeTag();
// Close any active elements.
while (!stackEmpty())
{
_writer.print("</");
_writer.print(pop());
_writer.print('>');
}
_writer.close();
_writer = null;
_filter = null;
_activeElementStack = null;
}
public void closeTag()
{
flushAttributes();
if (_emptyTag)
_writer.print(" /");
_writer.print('>');
_openTag = false;
_emptyTag = false;
}
/**
* Causes any pending attributes on the current open tag
* to be written out to the writer.
*/
void flushAttributes()
{
if (_attrMap.size() > 0) {
Iterator it = _attrMap.keySet().iterator();
while (it.hasNext()) {
String key = (String)it.next();
DefaultAttribute attr = (DefaultAttribute)_attrMap.get(key);
attr.print(key, _writer, _filter);
}
_attrMap.clear();
}
}
public void comment(String value)
{
if (_openTag)
closeTag();
_writer.print("<!-- ");
_writer.print(value);
_writer.println(" -->");
}
public void end()
{
if (_openTag)
closeTag();
if (stackEmpty())
throw new ApplicationRuntimeException(MarkupMessages.endWithEmptyStack());
_writer.print("</");
_writer.print(pop());
_writer.print('>');
}
public void end(String name)
{
if (_openTag)
closeTag();
if (_activeElementStack == null || !_activeElementStack.contains(name))
throw new ApplicationRuntimeException(MarkupMessages.elementNotOnStack(
name,
_activeElementStack));
while (true)
{
String tagName = pop();
_writer.print("</");
_writer.print(tagName);
_writer.print('>');
if (tagName.equals(name))
break;
}
}
public void flush()
{
_writer.flush();
}
public NestedMarkupWriter getNestedWriter()
{
return new NestedMarkupWriterImpl(this, _filter);
}
public void print(char[] data, int offset, int length)
{
print(data, offset, length, false);
}
public void printRaw(char[] buffer, int offset, int length)
{
print(buffer, offset, length, true);
}
public void print(char[] buffer, int offset, int length, boolean raw)
{
if (_openTag)
closeTag();
maybePrintFiltered(buffer, offset, length, raw, false);
}
public void print(String value)
{
print(value, false);
}
public void printRaw(String value)
{
print(value, true);
}
public void print(String value, boolean raw)
{
if (value == null || value.length() == 0)
{
print(null, 0, 0, raw);
return;
}
char[] buffer = value.toCharArray();
print(buffer, 0, buffer.length, raw);
}
public void print(char value)
{
char[] data = new char[]
{ value };
print(data, 0, 1);
}
public void print(int value)
{
if (_openTag)
closeTag();
_writer.print(value);
}
public void println()
{
if (_openTag)
closeTag();
_writer.println();
}
public String getContentType()
{
return _contentType;
}
private void checkTagOpen()
{
if (!_openTag)
throw new IllegalStateException(MarkupMessages.tagNotOpen());
}
private void push(String name)
{
if (_activeElementStack == null)
_activeElementStack = new ArrayList();
_activeElementStack.add(name);
}
private String pop()
{
int lastIndex = _activeElementStack.size() - 1;
return (String) _activeElementStack.remove(lastIndex);
}
private boolean stackEmpty()
{
return _activeElementStack == null || _activeElementStack.isEmpty();
}
}