/*
* 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 com.asual.summer.core.faces;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.ResponseWriter;
import org.springframework.util.StringUtils;
import com.sun.faces.util.HtmlUtils;
/**
*
* @author Rostislav Hristov
*
*/
public class FacesResponseWriter extends ResponseWriter {
private String contentType;
private String encoding;
private String previous;
private Writer writer;
private boolean closeStart;
private boolean escapeUnicode = true;
private boolean escapeIso = true;
private boolean isPartial;
private boolean scriptInAttributes;
private boolean start;
private char[] buffer = new char[1028];
private char[] textBuffer = new char[128];
private char[] charHolder = new char[1];
private StringWriter textWriter = new StringWriter();
private int depth = 0;
private String[] nodeDepth = new String[100];
private boolean[] nodeDepthContent = new boolean[100];
private String INDENT = " ";
private List<String> block = Arrays.asList(
"html", "head", "meta", "link", "title", "script", "style", "body",
"header", "footer", "section", "div", "h1", "h2", "h3", "h4", "h5", "h6",
"p", "dl", "dt", "dd", "ol", "ul", "li", "hr", "form", "fieldset", "legend",
"label", "input", "select", "option", "textarea", "button"
);
private List<String> pre = Arrays.asList(
"pre", "textarea"
);
public FacesResponseWriter(Writer writer, String contentType, String encoding,
boolean scriptInAttributes, boolean isPartial) throws FacesException {
this.writer = writer;
this.contentType = contentType;
this.encoding = encoding;
this.isPartial = isPartial;
this.scriptInAttributes = scriptInAttributes;
String charsetName = encoding.toUpperCase();
escapeUnicode = !HtmlUtils.isUTFencoding(charsetName);
escapeIso = !HtmlUtils.isISO8859_1encoding(charsetName) && !HtmlUtils.isUTFencoding(charsetName);
}
public String getContentType() {
return contentType;
}
public ResponseWriter cloneWithWriter(Writer writer) {
try {
return new FacesResponseWriter(writer, getContentType(), getCharacterEncoding(),
scriptInAttributes, isPartial);
} catch (FacesException e) {
throw new IllegalStateException();
}
}
public String getCharacterEncoding() {
return encoding;
}
public void close() throws IOException {
closeStartIfNecessary();
writer.close();
}
public void flush() throws IOException {
closeStartIfNecessary();
}
public void startDocument() throws IOException {
}
public void endDocument() throws IOException {
writer.flush();
}
public void startElement(String name, UIComponent componentForElement) throws IOException {
closeStartIfNecessary();
boolean indent = textWriter.toString().isEmpty();
printTextIfNecessary(false);
if (block.contains(name)|| (indent && previous != null && block.contains(previous))) {
if (!"html".equals(name)) {
writer.write('\n');
}
writer.write(getIndent(depth));
}
writer.write('<');
writer.write(name);
closeStart = true;
previous = name;
depth++;
nodeDepth[depth] = name;
nodeDepthContent[depth] = false;
}
public void endElement(String name) throws IOException {
boolean emptyNode = HtmlUtils.isEmptyElement(name);
start = start || closeStart;
if (emptyNode) {
if (closeStart) {
writer.write('>');
closeStart = false;
}
} else {
if (closeStart) {
writer.write('>');
closeStart = false;
}
printTextIfNecessary(true);
if (block.contains(name) && !pre.contains(name) &&
(!ComponentUtils.isStyleOrScript(name) || nodeDepthContent[depth])) {
writer.write('\n');
writer.write(getIndent(depth - 1));
}
writer.write("</" + name + ">");
previous = name;
}
nodeDepth[depth + 1] = null;
depth--;
}
private void printTextIfNecessary(boolean end) throws IOException {
String textStr = textWriter.toString();
if (StringUtils.hasText(textStr)) {
String name = nodeDepth[depth];
textStr = textStr.replaceAll("\t", INDENT);
if (ComponentUtils.isStyleOrScript(name)) {
textStr = Pattern.compile("^\\s{" + calculateIndent(textStr) + "}",
Pattern.MULTILINE).matcher(textStr).replaceAll(getIndent(depth)).trim();
}
if (block.contains(name) && !pre.contains(name)) {
Matcher m = Pattern.compile("^( *\\n)+.*", Pattern.DOTALL).matcher(textStr);
if (m.matches()) {
textStr = m.replaceFirst("$1") + getIndent(depth) + StringUtils.trimLeadingWhitespace(textStr);
} else if (start) {
textStr = "\n" + getIndent(depth) + StringUtils.trimLeadingWhitespace(textStr);
}
m = Pattern.compile(".*(\\n)+ *$", Pattern.DOTALL).matcher(textStr);
if (m.matches()) {
textStr = StringUtils.trimTrailingWhitespace(textStr) + m.replaceFirst("$1") + getIndent(depth);
}
}
if (end) {
textStr = StringUtils.trimTrailingWhitespace(textStr);
}
writer.write(textStr);
textWriter.getBuffer().setLength(0);
start = false;
nodeDepth[depth + 1] = null;
for (int i = 0; i < depth + 1; i++) {
nodeDepthContent[i] = true;
}
}
}
private void closeStartIfNecessary() throws IOException {
start = start || closeStart;
if (closeStart) {
writer.write('>');
closeStart = false;
}
}
public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException {
if (value instanceof Boolean) {
if (Boolean.TRUE.equals(value)) {
writer.write(' ');
writer.write(name);
}
} else if (value != null) {
writer.write(' ');
writer.write(name);
writer.write('=');
writer.write('"');
String val = value.toString();
ensureTextBufferCapacity(val);
HtmlUtils.writeAttribute(writer, escapeUnicode, escapeIso, buffer,
val, textBuffer, scriptInAttributes);
writer.write('"');
}
}
public void writeURIAttribute(String name, Object value, String componentPropertyName)
throws IOException {
writeAttribute(name, value, componentPropertyName);
}
public void write(char[] cbuf) throws IOException {
closeStartIfNecessary();
writer.write(cbuf);
}
public void write(int c) throws IOException {
closeStartIfNecessary();
writer.write(c);
}
public void write(char[] cbuf, int off, int len) throws IOException {
closeStartIfNecessary();
writer.write(cbuf, off, len);
}
public void write(String str) throws IOException {
ensureTextBufferCapacity(str);
textWriter.write(str);
closeStartIfNecessary();
}
public void write(String str, int off, int len) throws IOException {
ensureTextBufferCapacity(len);
textWriter.write(str, off, len);
closeStartIfNecessary();
}
public void writeText(char text) throws IOException {
closeStartIfNecessary();
charHolder[0] = text;
HtmlUtils.writeText(writer, escapeUnicode, escapeIso, buffer, charHolder);
}
public void writeText(char text[]) throws IOException {
closeStartIfNecessary();
HtmlUtils.writeText(writer, escapeUnicode, escapeIso, buffer, text);
}
public void writeText(Object text, String componentPropertyName) throws IOException {
if(!textWriter.toString().equals("<!DOCTYPE html>\n")){
String textStr = text.toString();
ensureTextBufferCapacity(textStr);
HtmlUtils.writeText(textWriter, escapeUnicode, escapeIso, buffer, textStr, textBuffer);
closeStartIfNecessary();
}
}
public void writeText(char text[], int off, int len) throws IOException {
closeStartIfNecessary();
HtmlUtils.writeText(writer, escapeUnicode, escapeIso, buffer, text, off, len);
}
public void writeComment(Object comment) throws IOException {
closeStartIfNecessary();
writer.write("<!--");
writer.write(comment.toString());
writer.write("-->");
}
private int calculateIndent(String textStr) {
int indent = 1000;
String[] lines = textStr.split("\n|\r\n");
for (String line : lines) {
if (StringUtils.hasText(line)) {
indent = Math.min(indent, line.length() - StringUtils.trimLeadingWhitespace(line).length());
}
}
return indent;
}
private String getIndent(int depth) throws IOException {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(INDENT);
}
return sb.toString();
}
private void ensureTextBufferCapacity(int len) {
if (textBuffer.length < len) {
textBuffer = new char[len * 2];
}
}
private void ensureTextBufferCapacity(String source) {
ensureTextBufferCapacity(source.length());
}
}