package org.faceletslite.imp;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.faceletslite.Configuration;
import org.faceletslite.CustomTag;
import org.faceletslite.Facelet;
import org.faceletslite.FaceletsCompiler;
import org.faceletslite.Namespace;
import org.faceletslite.ResourceReader;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class FaceletsCompilerImp implements FaceletsCompiler, CustomTag.Renderer
{
public interface Namespaces
{
String Ui = "http://java.sun.com/jsf/facelets";
String Core = "http://java.sun.com/jstl/core";
String JspCore = "http://java.sun.com/jsp/jstl/core";
String JsfH = "http://java.sun.com/jsf/html";
String Xhtml = "http://www.w3.org/1999/xhtml";
String None = "";
Set<String> CoreEquivalent = Const.setOf(Core, JspCore);
Set<String> UiEquivalent = Const.setOf(Ui);
}
public interface Environment {
String VarPrefix = "__facelet__";
String ResourceName = VarPrefix + "resourceName";
String ResourcePath = VarPrefix + "resourcePath";
String Namespace = VarPrefix + "namespace";
String SourceText = VarPrefix + "sourceText";
}
private static final Logger log = Logger.getLogger(FaceletsCompiler.class.getName());
private final Map<String, ResourceReader> resourceReaderByNsUri = new HashMap<String, ResourceReader>();
private final Map<String, Namespace> namespaceByUri = new HashMap<String, Namespace>();
private final ExpressionFactory expressionFactory;
private final FunctionMapper functionMapper;
private final ELResolver resolver;
private final Map<String, Facelet> templateCache;
private final Pool<DocumentBuilder> documentBuilderPool;
private final Pool<Transformer> documentTransformerPool;
public FaceletsCompilerImp()
{
this(new DefaultConfiguration());
}
public FaceletsCompilerImp(final Configuration configuration)
{
this.expressionFactory = configuration.getExpressionFactory();
this.templateCache = configuration.getCache();
this.resolver = configuration.getELResolver();
this.functionMapper = configuration.getFunctionMapper();
this.documentBuilderPool = new Pool<DocumentBuilder>() {
@Override public DocumentBuilder create() {
DocumentBuilder result = configuration.createDocumentBuilder();
if (!result.isNamespaceAware()) {
throw new RuntimeException("document builder factory must be set to namespace-aware.");
}
result.reset();
return result;
}
};
this.documentTransformerPool = new Pool<Transformer>() {
@Override public Transformer create() {
Transformer result = configuration.createDocumentTransformer();
result.reset();
return result;
}
};
ResourceReader standardResourceReader = configuration.getResourceReader();
if (standardResourceReader!=null) {
resourceReaderByNsUri.put(Namespaces.None, standardResourceReader);
}
for (Namespace namespace: configuration.getCustomNamespaces())
{
String nsUri = namespace.getUri();
namespaceByUri.put(nsUri, namespace);
ResourceReader resourceReader = namespace.getResourceReader();
if (resourceReader!=null) {
resourceReaderByNsUri.put(nsUri, resourceReader);
}
}
}
@Override public FaceletImp compile(InputStream in) throws IOException
{
return new FaceletImp(read(in), "", Namespaces.None);
}
@Override public FaceletImp compile(String resourceName) throws IOException
{
return compile(resourceName, null);
}
@Override public FaceletImp compile(String resourceName, String nsUri) throws IOException
{
String key = resourceName;
if (nsUri==null) {
nsUri = Namespaces.None;
}
if (!Namespaces.None.equals(nsUri)) {
key = nsUri+"/"+key;
}
FaceletImp result = templateCache==null ? null : (FaceletImp)templateCache.get(key);
if (result==null) {
result = new FaceletImp(read(resourceName, nsUri), resourceName, nsUri);
if (templateCache!=null) {
templateCache.put(key, result);
}
}
return result;
}
private String read(String resourceName, String nsUri) throws IOException
{
ResourceReader resourceReader = resourceReaderByNsUri.get(nsUri);
if (resourceReader==null) {
throw new IOException("no resource reader to read "+getResourceInfo(resourceName, nsUri));
}
return read(resourceReader.read(resourceName));
}
private String getResourceInfo(String resourceName, String nsUri)
{
String resourceInfo ="resource '"+resourceName+"'";
if (!Namespaces.None.equals(nsUri)) {
resourceInfo += ", namespace "+nsUri;
}
return resourceInfo;
}
public String read(InputStream in) throws IOException
{
try {
InputStreamReader reader = new InputStreamReader(in, "utf-8");
StringBuilder builder = new StringBuilder();
char[] buffer = new char[2048];
int read;
while ((read = reader.read(buffer)) > 0) {
// skip signature, if any
if (buffer[0] == '\uFEFF') {
builder.append(buffer, 1, read - 1);
} else {
builder.append(buffer, 0, read);
}
}
return builder.toString();
}
finally {
in.close();
}
}
@Override
public String html(Node node)
{
StringWriter writer = new StringWriter();
if (node instanceof Document) {
String docType = Dom.getDocType(((Document)node));
if (Is.notEmpty(docType)) {
writer.write(docType + "\r\n");
}
}
Transformer documentWriter = documentTransformerPool.get();
documentWriter.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
documentWriter.setOutputProperty(OutputKeys.INDENT, "no");
documentWriter.setOutputProperty(OutputKeys.METHOD, "html");
documentWriter.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
try
{
documentWriter.transform(new DOMSource(node), new StreamResult(writer));
writer.flush();
return writer.toString();
}
catch (TransformerException exc)
{
throw new RuntimeException("cannot write", exc);
}
finally
{
documentTransformerPool.release(documentWriter);
}
}
private Document newDocument()
{
DocumentBuilder builder = documentBuilderPool.get();
try {
return builder.newDocument();
}
finally {
documentBuilderPool.release(builder);
}
}
class FaceletImp implements Facelet
{
private final Pool<Document> sourceDocumentWorkingCopies;
private final String resourceName;
private final String namespace;
private final String sourceText;
private final Map<String, String> environmentVars;
//private final String sourceDocType;
private final String sourceDocTypeName;
private final String sourceDocTypePublicId;
private final String sourceDocTypeSystemId;
FaceletImp(String sourceText, String resourceName, String namespace) throws IOException
{
this.sourceText = sourceText;
this.resourceName = resourceName;
this.namespace = namespace;
final Document sourceDocument = parse();
//this.sourceDocType = Dom.getDocType(sourceDocument);
DocumentType sourceDocType = sourceDocument.getDoctype();
sourceDocTypeName = sourceDocType==null ? null : sourceDocType.getName();
sourceDocTypePublicId = sourceDocType==null ? null : sourceDocType.getPublicId();
sourceDocTypeSystemId = sourceDocType==null ? null : sourceDocType.getSystemId();
// even read access to a document is not thread-safe, so we pool!
this.sourceDocumentWorkingCopies = new Pool<Document>() {
@Override
protected Document create() {
Transformer transformer = documentTransformerPool.get();
try {
synchronized (sourceDocument) {
DOMSource source = new DOMSource(sourceDocument);
DOMResult result = new DOMResult();
transformer.transform(source,result);
return (Document)result.getNode();
}
}
catch (TransformerException exc) {
throw new RuntimeException("cannot clone source document", exc);
}
finally {
documentTransformerPool.release(transformer);
}
};
};
this.environmentVars = new HashMap<String, String>();
environmentVars.put(Environment.Namespace, namespace);
environmentVars.put(Environment.ResourceName, resourceName);
environmentVars.put(Environment.ResourcePath, getResourcePath());
environmentVars.put(Environment.SourceText, sourceText);
}
public Map<String, String> getEnvironmentVars() {
return environmentVars;
}
private Document parse() throws IOException
{
DocumentBuilder builder = documentBuilderPool.get();
StringReader reader = new StringReader(sourceText);
try {
Document document = builder.parse(new InputSource(reader));
// document.normalizeDocument();
return document;
}
catch (SAXException exc)
{
String resourceInfo = getResourceInfo(resourceName, namespace);
if (exc instanceof SAXParseException) {
SAXParseException parseExc = (SAXParseException)exc;
int line = parseExc.getLineNumber();
int col = parseExc.getColumnNumber();
if (line>=0) {
resourceInfo += ", line "+line;
if (col>0) {
resourceInfo += ", column "+col;
}
}
}
throw new RuntimeException("cannot parse "+resourceInfo+":\r\n\t"+exc.getMessage(), exc);
}
finally {
reader.close();
documentBuilderPool.release(builder);
}
}
public String getResourceName()
{
return resourceName;
}
public String getNamespace()
{
return namespace;
}
@Override
public String toString()
{
return sourceText;
}
String getResourcePath()
{
String result = "";
int lastIndexOfSlash = resourceName.lastIndexOf("/");
if (lastIndexOfSlash>=0) {
result = resourceName.substring(0, lastIndexOfSlash+1);
}
return result;
}
String normalizeResourceNamePath(String resourceName)
{
boolean absolute = resourceName.startsWith("/");
if (!absolute) {
resourceName = getResourcePath() + resourceName;
}
return resourceName;
}
RuntimeException error(String message, Exception reason)
{
message += "\r\n\t(while parsing '"+getResourceName()+"'";
if (Is.notEmpty(namespace)) {
message += ", namespace "+namespace;
}
message += ")";
throw new RuntimeException(message, reason);
}
RuntimeException error(String message)
{
throw error(message, null);
}
@Override
public String render(Object scope)
{
Document targetDocument = newDocument();
List<Node> processedNodes = process(targetDocument, new MutableContext().scope(scope), null);
Node target = hasDocType(processedNodes) ? targetDocument : targetDocument.createDocumentFragment();
Dom.appendChildren(target, processedNodes);
return html(target);
}
boolean hasDocType(List<Node> nodes) {
if (nodes.size()>0) {
if (nodes.get(0) instanceof DocumentType) {
return true;
}
}
return false;
}
List<Node> process(Document targetDocument, MutableContext context, Map<String, SourceFragment> defines)
{
return process(new Processor(targetDocument, context, defines));
}
List<Node> process(Processor processor)
{
Document workingCopy = sourceDocumentWorkingCopies.get();
try
{
Node sourceRoot = getRootNode(workingCopy);
List<Node> result = new ArrayList<Node>();
if (Is.notEmpty(sourceDocTypeName)) {
result.add(
processor
.getTargetDocument()
.getImplementation()
.createDocumentType(
sourceDocTypeName,
sourceDocTypePublicId,
sourceDocTypeSystemId
)
);
}
result.addAll(processor.compile(sourceRoot));
return result;
}
finally {
sourceDocumentWorkingCopies.release(workingCopy);
}
}
Node getRootNode(Document sourceDocument)
{
for (Element composition: Dom.elementsByTagName(sourceDocument, Namespaces.Ui, "composition")) {
return composition;
}
for (Element component: Dom.elementsByTagName(sourceDocument, Namespaces.Ui, "component")) {
return component;
}
return sourceDocument;
}
class Processor implements CustomTag.Processor
{
private final Document targetDocument;
private final Map<String, SourceFragment> defines;
private MutableContext context;
private boolean swallowComments = true;
private String targetDocType;
public Processor(Document targetDocument, MutableContext context, Map<String, SourceFragment> defines)
{
this.targetDocument = targetDocument;
this.context = context;
this.defines = defines;
}
@Override
public Document getTargetDocument()
{
return targetDocument;
}
Processor with(MutableContext context, Map<String, SourceFragment> defines)
{
return new Processor(targetDocument, context, defines);
}
List<Node> compileHtmlTag(Element sourceElement)
{
Element targetElement = getTargetDocument().createElement(sourceElement.getTagName());
for (Attr attr: Dom.attrs(sourceElement))
{
String name = attr.getName();
if (isHtmlNamespace(Dom.nsUri(attr)) && !name.startsWith("xmlns")) {
String newValue = eval(attr);
if (Is.notEmpty(newValue)) {
targetElement.setAttribute(name, newValue);
}
}
}
Dom.appendChildren(targetElement, compileChildren(sourceElement));
return nodes(targetElement);
}
List<Node> compileJspCoreTag(Element element)
{
String tagName = element.getLocalName();
if ("set".equals(tagName)) {
Object value = attr(element, "value", Object.class);
String var = requiredAttr(element, "var", String.class);
context = context.put(var, value);
return nodes();
}
if ("if".equals(tagName)) {
Object test = attr(element, "test", Object.class);
String var = attr(element, "var", String.class);
if (Is.conditionTrue(test)) {
return with(
context.put(var, test),
defines
).compileChildren(element);
}
return nodes();
}
if ("forEach".equalsIgnoreCase(tagName)) {
List<Node> result = new ArrayList<Node>();
Iterable<?> _items = attr(element, "items", Iterable.class);
List<Object> items = new ArrayList<Object>();
if (_items!=null) {
for (Object item: _items) {
items.add(item);
}
}
String var = attr(element, "var", String.class);
String varStatus = attr(element, "varStatus", String.class);
String begin = attr(element, "begin", String.class);
String end = attr(element, "end", String.class);
String step = attr(element, "step", String.class);
LoopTagStatusImp status = new LoopTagStatusImp(
Safe.toInt(begin, 0),
Safe.toInt(end, items.size()-1),
Safe.toInt(step, 1),
items
);
while (status.hasNext()) {
result.addAll(
with(
context.nest() // ??
.put(var, status.getCurrent())
.put(varStatus, status),
defines
).compileChildren(element)
);
status.next();
}
return result;
}
if ("choose".equals(tagName)) {
for (Element when: Dom.childrenByTagName(element, Namespaces.CoreEquivalent, "when")) {
boolean test = requiredAttr(when, "test", Boolean.class);
if (test) {
return compileChildren(when);
}
}
for (Element otherwise: Dom.childrenByTagName(element, Namespaces.CoreEquivalent, "otherwise")) {
return compileChildren(otherwise);
}
return nodes();
}
throw error("invalid core tag name '"+tagName+"'");
}
List<Node> compileUiTag(Element element)
{
String tagName = element.getLocalName();
if ("with".equals(tagName)) {
Object value = attr(element, "value", Object.class);
MutableContext newContext = value==null ? context : context.nest().scope(value);
return with(newContext, defines).compileChildren(element);
}
if ("include".equals(tagName)) {
String src = attr(element, "src", String.class);
String namespace = attr(element, "namespace", String.class);
MutableContext newContext = collectParams(element);
if (Is.empty(src)) {
return with(newContext, defines).compileChildren(element);
}
else {
try {
FaceletImp template = FaceletsCompilerImp.this.compile(normalizeResourceNamePath(src), namespace==null ? getNamespace() : namespace);
with(newContext, defines).compileChildren(template.getRootNode(targetDocument));
return template.process(targetDocument, newContext, defines);
}
catch (IOException exc) {
throw error("cannot include '"+src+"'", exc);
}
}
}
if ("insert".equals(tagName)) {
String name = attr(element, "name", String.class);
if (name==null) { name = ""; }
SourceFragment fragment = defines==null ? null : defines.get(name);
if (fragment==null) {
return compileChildren(element);
}
else {
return with(context, fragment.getDefinitions())
.compileChildren(fragment.getRoot());
}
}
if ("composition".equals(tagName))
{
String templateAttr = attr(element, "template", String.class);
if (Is.empty(templateAttr)) {
return compileChildren(element);
}
else {
return applyTemplate(element, templateAttr);
}
}
if ("component".equals(tagName) || "fragment".equals(tagName))
{
return compileChildren(element);
}
if ("decorate".equals(tagName)) {
String template = requiredAttr(element, "template", String.class);
return applyTemplate(element, template);
}
if ("debug".equals(tagName)) {
log.log(Level.WARNING, "ignoring ui debug tag");
return nodes();
}
if ("remove".equals(tagName)) {
// nothing
return nodes();
}
if ("param".equals(tagName)) {
// ignore (already processed)
return nodes();
}
if ("define".equals(tagName)) {
// ignore (already processed)
return nodes();
}
throw error("invalid ui tag name '"+tagName+"'");
}
List<Node> compileJsfHTag(Element element)
{
String tagName = element.getLocalName();
if ("outputText".equals(tagName)) {
String value = attr(element, "value", String.class);
if (Is.empty(value)) {
return nodes();
}
else {
Boolean escape = attr(element, "escape", Boolean.class);
return text(value, escape==null || !escape.equals(Boolean.FALSE));
}
}
throw error("invalid h tag name '"+tagName+"'");
}
List<Node> compileCustomTag(Element element)
{
String tagName = element.getLocalName();
String nsUri = Dom.nsUri(element);
Namespace namespace = namespaceByUri.get(nsUri);
if (namespace!=null) {
CustomTag customTag = namespace.getCustomTag(tagName);
if (customTag!=null) {
return customTag.process(element, this, (CustomTag.Renderer)FaceletsCompilerImp.this);
}
}
MutableContext newContext = context.nest();
for (Attr attr: Dom.attrs(element)) {
newContext.put(
attr.getName(),
eval(attr.getValue(), Object.class)
);
}
try {
FaceletImp template = FaceletsCompilerImp.this.compile(tagName, nsUri);
return template.process(with(
newContext,
collectDefines(element)
));
}
catch (IOException exc) {
throw error("cannot load "+element.getPrefix()+":"+tagName, exc);
}
}
List<Node> compile(Node sourceNode)
{
if (sourceNode instanceof Document) {
return compile(sourceNode.getChildNodes());
}
else if (sourceNode instanceof Text) {
Text text = (Text) sourceNode;
String sourceText = text.getData();
String targetText = eval(sourceText, String.class);
if (Is.empty(targetText)) {
return nodes();
}
else {
return text(targetText, true);
}
}
else if (sourceNode instanceof Element) {
Element sourceElement = (Element)sourceNode;
String nsUri = Dom.nsUri(sourceElement);
if (isHtmlNamespace(nsUri)) {
return compileHtmlTag(sourceElement);
}
else if (Namespaces.Core.equals(nsUri) || Namespaces.JspCore.equals(nsUri)) {
return compileJspCoreTag(sourceElement);
}
else if (Namespaces.Ui.equals(nsUri)) {
return compileUiTag(sourceElement);
}
else if (Namespaces.JsfH.equals(nsUri)) {
return compileJsfHTag(sourceElement);
}
else {
return compileCustomTag(sourceElement);
}
}
else if (sourceNode instanceof Comment) {
if (!swallowComments) {
Comment comment = (Comment)sourceNode;
String commentText = comment.getData();
return nodes(
getTargetDocument().createComment(commentText)
);
}
}
else if (sourceNode instanceof ProcessingInstruction)
{
ProcessingInstruction instruction = (ProcessingInstruction)sourceNode;
if ("facelets".equals(instruction.getTarget())) {
String data = instruction.getData().trim();
if (data.equals("suspendEvaluation")) {
context = context.nest().suspend(true);
}
if (data.equals("swallowComments='true'") || data.equals("swallowComments=\"true\"") || data.equals("swallowComments=true")) {
swallowComments = true;
}
if (data.equals("swallowComments='false'") || data.equals("swallowComments=\"false\"") || data.equals("swallowComments=false")) {
swallowComments = false;
}
}
}
return nodes();
}
List<Node> compile(NodeList sourceNodes)
{
List<Node> result = new ArrayList<Node>();
for (Node node: Dom.iterate(sourceNodes)) {
result.addAll(
compile(node)
);
}
return result;
}
@Override
public List<Node> compileChildren(Node sourceNode)
{
return compile(sourceNode.getChildNodes());
}
public List<Node> applyTemplate(Element sourceElement, String templateAttr)
{
try {
FaceletImp template = FaceletsCompilerImp.this.compile(normalizeResourceNamePath(templateAttr));
return template.process(
getTargetDocument(),
collectParams(sourceElement),
collectDefines(sourceElement)
);
}
catch (IOException exc) {
throw error("cannot read template '"+templateAttr+"'", exc);
}
}
@Override
public List<Node> text(String text, boolean escape)
{
List<Node> result = new ArrayList<Node>();
Document document = getTargetDocument();
if (!escape) {
result.add(
document.createProcessingInstruction(StreamResult.PI_DISABLE_OUTPUT_ESCAPING, "")
);
}
result.add(document.createTextNode(text));
if (!escape) {
result.add(
document.createProcessingInstruction(StreamResult.PI_ENABLE_OUTPUT_ESCAPING, "")
);
}
return result;
}
public List<Node> nodes(Node... nodes)
{
switch (nodes.length) {
case 0:
return Collections.emptyList();
case 1:
return Collections.singletonList(nodes[0]);
}
List<Node> result = new ArrayList<Node>();
for (Node node: nodes) {
result.add(node);
}
return result;
}
Map<String, SourceFragment> collectDefines(Element parent)
{
Map<String, SourceFragment> result = new HashMap<String, SourceFragment>();
if (defines!=null) {
result.putAll(defines);
}
for (Element define: Dom.childrenByTagName(parent, Namespaces.UiEquivalent, "define")) {
String name = requiredAttr(define, "name", String.class);
result.put(name, new SourceFragment(define, context, defines));
}
result.put("", new SourceFragment(parent, context, defines));
return result;
}
MutableContext collectParams(Element parent)
{
MutableContext result = context.nest();
for (Element param: Dom.childrenByTagName(parent, Namespaces.UiEquivalent, "param")) {
result.put(
requiredAttr(param, "name", String.class),
attr(param, "value", Object.class)
);
}
return result;
}
boolean isHtmlNamespace(String nsUri)
{
return Is.empty(nsUri) || Namespaces.Xhtml.equals(nsUri);
}
String eval(Attr attr)
{
return eval(attr.getValue(), String.class);
}
@Override
public <T> T attr(Element element, String name, Class<T> clazz)
{
String value = element.getAttribute(name);
return Is.empty(value) ? null : eval(value, clazz);
}
@Override
public <T> T requiredAttr(Element element, String name, Class<T> clazz)
{
T result = attr(element, name, clazz);
if (Is.empty(result)) {
throw error("missing attribute '"+name+"' in "+element.getTagName());
}
return result;
}
@SuppressWarnings("unchecked")
<T> T eval(String text, Class<T> clazz)
{
try {
return context.eval(
text,
clazz,
getEnvironmentVars()
);
}
catch (RuntimeException exc) {
String message = text+" expression evaluation failed:\r\n\t"+exc.getMessage();
throw error(message, exc);
}
}
}
}
class SourceFragment
{
private final Node root;
private final MutableContext context;
private final Map<String, SourceFragment> defines;
public SourceFragment(Node root, MutableContext context, Map<String, SourceFragment> defines) {
this.root = root;
this.context = context;
this.defines = defines;
}
public Node getRoot()
{
return root;
}
public MutableContext getContext()
{
return context;
}
public Map<String, SourceFragment> getDefinitions()
{
return defines;
}
@Override
public String toString()
{
return root.toString();
}
}
class MutableContext extends ELContext
{
private final ELContext fallback;
private Object scope;
private Object environmentVars;
private boolean suspended;
private final Map<String, ValueExpression> variables = new LinkedHashMap<String, ValueExpression>();
private final VariableMapper variableMapper = new VariableMapper()
{
@Override public ValueExpression setVariable(String name, ValueExpression expr)
{
variables.put(name, expr);
return expr;
}
@Override public ValueExpression resolveVariable(String name)
{
Object value = scope==null ? null : resolver.getValue(MutableContext.this, scope, name);
if (value!=null) {
return wrap(value);
}
value = environmentVars==null ? null : resolver.getValue(MutableContext.this, environmentVars, name);
if (value!=null) {
return wrap(value);
}
ValueExpression expr = variables.get(name);
if (expr!=null) {
return expr;
}
return fallback==null ? wrap(null) : fallback.getVariableMapper().resolveVariable(name);
}
};
MutableContext()
{
this(null);
}
MutableContext(ELContext fallback)
{
this.fallback = fallback;
this.suspended = (fallback instanceof MutableContext) ? ((MutableContext)fallback).suspended : false;
}
MutableContext nest()
{
return new MutableContext(this);
}
MutableContext scope(Object scope)
{
this.scope = scope;
return this;
}
MutableContext suspend(boolean suspend)
{
this.suspended = suspend;
return this;
}
MutableContext put(String name, Object value)
{
if (name!=null) {
variableMapper.setVariable(name, wrap(value));
}
return this;
}
private ValueExpression wrap(Object value)
{
return expressionFactory.createValueExpression(value, Object.class);
}
@Override public ELResolver getELResolver()
{
return resolver;
}
@Override public FunctionMapper getFunctionMapper()
{
return functionMapper;
}
@Override public VariableMapper getVariableMapper()
{
return variableMapper;
}
@SuppressWarnings("unchecked")
<T> T eval(String text, Class<?> clazz, Object environmentVars) {
if (suspended) {
if (clazz==String.class || clazz==Object.class) {
return (T)text;
}
}
this.environmentVars = environmentVars;
return (T)expressionFactory
.createValueExpression(this, text, clazz)
.getValue(this)
;
}
}
public static class LoopTagStatusImp
{
private int begin;
private int end;
private int step;
private int index;
private int count = 1;
private final List<?> items;
public LoopTagStatusImp(int begin, int end, int step, List<?> items) {
this.begin = begin;
this.end = end;
this.step = step;
this.index = begin;
this.items = items;
}
public Object getCurrent() { return Safe.get(items, index); }
public int getIndex() { return index; }
public int getCount() { return count; }
public boolean isFirst() { return index==begin; }
public boolean isLast() { return index==end; }
public boolean isEven() { return index % 2 == 0; }
public boolean isOdd() { return index % 2 == 1; }
public Integer getBegin() { return begin; }
public Integer getEnd() { return end; }
public Integer getStep() { return step; }
public boolean hasNext() { return index<=end; }
public void next() { index += step; count += 1; }
}
static class Dom
{
static Iterable<Node> iterate(NodeList nodeList)
{
List<Node> nodes = new ArrayList<Node>();
if (nodeList!=null) {
for (int i=0; i<nodeList.getLength(); ++i) {
nodes.add(nodeList.item(i));
}
}
return nodes;
}
static Iterable<Element> elementsByTagName(Node parent, String nsUri, String tagName)
{
if (parent instanceof Document) {
return elements(((Document)parent).getElementsByTagNameNS(nsUri, tagName));
}
if (parent instanceof Element) {
return elements(((Element)parent).getElementsByTagNameNS(nsUri, tagName));
}
return Collections.emptyList();
}
static Iterable<Element> childrenByTagName(Node parent, Set<String> nsUris, String tagName)
{
List<Element> elements = new ArrayList<Element>();
NodeList nodeList = parent.getChildNodes();
for (int i=0; i<nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
if (node instanceof Element) {
Element element = (Element)node;
if (element.getLocalName().equals(tagName) && nsUris.contains(nsUri(element)))
{
elements.add((Element)node);
}
}
}
return elements;
}
static Iterable<Element> elements(NodeList nodeList)
{
List<Element> elements = new ArrayList<Element>();
for (int i=0; i<nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
if (node instanceof Element) {
elements.add((Element)node);
}
}
return elements;
}
static Iterable<Attr> attrs(Element element)
{
return attrs(element.getAttributes());
}
static Iterable<Attr> attrs(NamedNodeMap nodeMap)
{
List<Attr> attrs = new ArrayList<Attr>();
for (int i=0; i<nodeMap.getLength(); ++i) {
Node node = nodeMap.item(i);
if (node instanceof Attr) {
attrs.add((Attr)node);
}
}
return attrs;
}
static void appendChildren(Node parent, List<Node> children)
{
for (Node child: children) {
parent.appendChild(child);
}
}
static Document document(Node parent)
{
return (parent instanceof Document) ? (Document)parent : parent.getOwnerDocument();
}
static String nsUri(Node node)
{
//return node.getNamespaceURI(); <-- buggy in old XML implementations, such as the JDK's default one
return node.lookupNamespaceURI(node.getPrefix());
}
static String getDocType(Document document)
{
DocumentType docType = document.getDoctype();
if (docType==null) {
return null;
}
String docTypeName = docType.getName();
String publicId = docType.getPublicId();
String systemId = docType.getSystemId();
if (Is.empty(docTypeName)) {
return null;
}
StringBuffer result = new StringBuffer("<!DOCTYPE ");
result.append(docTypeName);
if (Is.notEmpty(publicId)) {
result.append(" PUBLIC \"");
result.append(publicId);
result.append('"');
}
if (Is.notEmpty(systemId)) {
result.append(" \"");
result.append(systemId);
result.append('"');
}
result.append(">");
return result.toString();
}
}
static abstract class Pool<T>
{
private final ConcurrentLinkedQueue<T> container = new ConcurrentLinkedQueue<T>();
public T get()
{
T result = container.poll();
if (result==null) {
result = create();
}
return result;
}
public void release(T object)
{
container.offer(object);
}
protected abstract T create();
}
static class Const
{
public static <T> Set<T> setOf(T... objects)
{
Set<T> result = new LinkedHashSet<T>(objects.length);
for (T object: objects) {
result.add(object);
}
return Collections.unmodifiableSet(result);
}
}
static class Safe
{
@SuppressWarnings("unchecked")
public static boolean equals(Object obj1, Object obj2)
{
if (obj1==obj2) {
return true;
}
if (obj1==null || obj2==null) {
return false;
}
Class<?> class1 = obj1.getClass();
Class<?> class2 = obj2.getClass();
if (class1.isArray() && class2.isArray()) {
Object[] array1 = (Object[])obj1;
Object[] array2 = (Object[])obj2;
return Arrays.deepEquals(array1, array2);
}
return obj1.equals(obj2);
}
public static <T> T get(List<T> list, int index)
{
return get(list, index, null);
}
public static <T> T get(List<T> list, int index, T _default)
{
return list!=null && 0<=index && index<list.size() ? list.get(index) : _default;
}
public static int toInt(Object object, int _default)
{
if (object!=null) {
if (object instanceof Number) {
return ((Number)object).intValue();
}
if (object instanceof CharSequence) {
try {
return Integer.parseInt(object.toString());
}
catch (NumberFormatException exc) {
}
}
}
return _default;
}
}
static class Is
{
static boolean conditionTrue(Object object)
{
if (object==null) {
return false;
}
if (object instanceof Boolean) {
return (Boolean)object;
}
if (object instanceof String) {
return !((String)object).trim().equalsIgnoreCase("false");
}
return true;
}
static boolean empty(Object object)
{
if (object==null) {
return true;
}
if (object instanceof String) {
return ((String)object).length()==0;
}
if (object instanceof List<?>) {
return ((List<?>)object).size()==0;
}
return false;
}
static boolean notEmpty(Object object)
{
return !empty(object);
}
}
}