All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.thymeleaf.dom.Node Maven / Gradle / Ivy

Go to download

Modern server-side Java template engine for both web and standalone environments

There is a newer version: 3.1.3.RELEASE
Show newest version
/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2012, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   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.thymeleaf.dom;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.thymeleaf.Arguments;
import org.thymeleaf.Configuration;
import org.thymeleaf.processor.ProcessorAndContext;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.util.IdentityCounter;
import org.thymeleaf.util.Validate;



/**
 * 

* Base abstract class for all nodes in a Thymeleaf DOM tree. *

* * @author Daniel Fernández * * @since 2.0.0 * */ public abstract class Node implements Serializable { private static final long serialVersionUID = 3082306990735650683L; private static final int DEFAULT_NODE_LOCAL_VARIABLES_MAP_SIZE = 3; /** *

* Name of the property set by XML-based parsers into {@link Document} nodes * for specifying the "XML Encoding" info from the XML Declaration. *

*

* Value: "XML_ENCODING" *

*/ public static final String NODE_PROPERTY_XML_ENCODING = "XML_ENCODING"; /** *

* Name of the property set by XML-based parsers into {@link Document} nodes * for specifying the "XML Version" info from the XML Declaration. *

*

* Value: "XML_VERSION" *

*/ public static final String NODE_PROPERTY_XML_VERSION = "XML_VERSION"; /** *

* Name of the property set by XML-based parsers into {@link Document} nodes * for specifying the "XML Standalone" flag from the XML Declaration. *

*

* Value: "XML_STANDALONE" *

*/ public static final String NODE_PROPERTY_XML_STANDALONE = "XML_STANDALONE"; private final String documentName; private final Integer lineNumber; private final boolean shouldConsiderAsElementForProcessing; NestableNode parent; private boolean skippable; private boolean precomputed; private boolean recomputeProcessorsAfterEachExecution; private boolean recomputeProcessorsImmediately; private HashMap nodeLocalVariables; private ArrayList processors; private HashMap nodeProperties; /** *

* Normalizes an element or attribute name by converting it * to lower-case. Elements and attributes are processed as * case-insensitive, and this method allows normalizing their * names before processing. *

* * @param name the name to be normalized. * @return the normalized name. */ public static String normalizeName(final String name) { if (name == null) { return null; } return name.toLowerCase(); } /** *

* Applies a prefix (a dialect prefix) to the specified name. *

*

* The result looks like: "${prefix}:${name}". *

* * @param name the name to be prefixed * @param dialectPrefix the prefix to be applied * @return the prefixed name */ public static String applyDialectPrefix(final String name, final String dialectPrefix) { if (name == null) { return null; } if (dialectPrefix == null || dialectPrefix.trim().equals("")) { return name; } return dialectPrefix + ":" + name; } Node(final String documentName, final Integer lineNumber) { super(); this.documentName = documentName; this.lineNumber = lineNumber; this.skippable = false; this.precomputed = false; this.recomputeProcessorsAfterEachExecution = false; this.recomputeProcessorsImmediately = false; this.nodeLocalVariables = null; this.processors = null; this.shouldConsiderAsElementForProcessing = (this instanceof Element || this instanceof Document); this.nodeProperties = null; } /** *

* Returns the name of the document this node comes from. Can be null, * if node does not come from parsing a document (for example, could come * from parsing an externalized message). *

* * @return the document name */ public String getDocumentName() { return this.documentName; } /** *

* Returns the number of the line where this node appears in the original template * document. Might be null if the parser used to create the node is not able to * obtain this information for nodes, or if this kind of information does not * apply (for example, when parsing externalized messages). *

* * @return the line number */ public Integer getLineNumber() { return this.lineNumber; } /** *

* Sets a value for a node property. *

*

* Node properties might contain arbitrary information, normally * set by parsers into node objects for use from processors. *

*

* Users might need to create parsers that add specific properties to some * nodes in order to give processing hints to their processors or result * writers. *

* * @param name the name of the property * @param value the value of the property */ public final void setNodeProperty(final String name, final Object value) { Validate.notNull(name, "Property name cannot be null"); if (this.nodeProperties == null) { this.nodeProperties = new HashMap(); } this.nodeProperties.put(name, value); } /** *

* Returns whether a node contains a specific node property * or not. *

*

* Node properties might contain arbitrary information, normally * set by parsers into node objects for use from processors. *

*

* Users might need to create parsers that add specific properties to some * nodes in order to give processing hints to their processors or result * writers. *

* * @param name the name of the property to check * @return true if the node contains the property, false if not */ public final boolean hasNodeProperty(final String name) { Validate.notNull(name, "Property name cannot be null"); if (this.nodeProperties == null) { return false; } return this.nodeProperties.containsKey(name); } /** *

* Returns the value of a specific node property, or null * if the property has not been set. *

*

* Node properties might contain arbitrary information, normally * set by parsers into node objects for use from processors. *

*

* Users might need to create parsers that add specific properties to some * nodes in order to give processing hints to their processors or result * writers. *

* * @param name the name of the property to be retrieved * @return the value of the property */ public final Object getNodeProperty(final String name) { Validate.notNull(name, "Property name cannot be null"); if (this.nodeProperties == null) { return null; } return this.nodeProperties.get(name); } /** *

* Returns the names of all the currently set node properties. *

*

* Node properties might contain arbitrary information, normally * set by parsers into node objects for use from processors. *

*

* Users might need to create parsers that add specific properties to some * nodes in order to give processing hints to their processors or result * writers. *

* * @return the set of property names. */ public final Set getNodePropertyNames() { if (this.nodeProperties == null) { return Collections.emptySet(); } return Collections.synchronizedSet(this.nodeProperties.keySet()); } /** *

* Returns the real inner map object containing the node properties. This * method is only meant for internal use. DO NOT call this * method directly from your code. *

* * @return the map of node properties. */ public final Map unsafeGetNodeProperties() { return this.nodeProperties; } /** *

* Returns whether this node has a parent node or not. *

*

* A node does not have a parent if its parent property * is set to null. {@link Document} nodes always have no parent, and * other types of node might also have null parent if they represent * the root of a DOM subtree. *

* * @return true if the node has a parent, false if not. */ public final boolean hasParent() { return this.parent != null; } /** *

* Returns the parent of a node. Will return null if this * node has no parent. *

* * @return the parent of the node, or null if there is no parent. */ public final NestableNode getParent() { return this.parent; } /** *

* Sets a new parent for the node. *

* * @param parent the new parent. */ public final void setParent(final NestableNode parent) { this.parent = parent; } /** *

* Returns the value of a flag indicating whether the list of processors to be applied * to this node should be recomputed after the execution of each processor. *

*

* This flag is usually set by processors with very high precedence in order to signal * the fact that they would like the engine to recompute the list of processors to be * applied to the node after each of the subsequent processors finish their execution. *

* * @return true if the list of applicable processors must be recomputed after each execution, * false if not. */ public final boolean getRecomputeProcessorsAfterEachExecution() { return this.recomputeProcessorsAfterEachExecution; } /** *

* Sets the value of a flag indicating whether the processors to be applied * to this node should be recomputed after the execution of each processor. *

*

* This flag is usually set by processors with very high precedence in order to signal * the fact that they would like the engine to recompute the list of processors to be * applied to the node after each of the subsequent processors finish their execution. *

* * @param recomputeProcessorsAfterEachExecution the new value for the flag */ public final void setRecomputeProcessorsAfterEachExecution(final boolean recomputeProcessorsAfterEachExecution) { this.recomputeProcessorsAfterEachExecution = recomputeProcessorsAfterEachExecution; } /** *

* Returns the value of a flag indicating whether the processors to be applied * to this node should be recomputed once after the execution of the current processor * ends. *

*

* This flag is usually set by processors in order to signal the fact that they have * modified the node in a way that should modify the list of processors to be applied * to it (for example, by adding an attribute which might have a processor associated). *

*

* If this flag is set to true, the engine will automatically recompute the applicable * processors before looking for the next processor to execute. *

* * @return true if the list of applicable processors must be recomputed, false if not. */ public final boolean getRecomputeProcessorsImmediately() { return this.recomputeProcessorsImmediately; } /** *

* Sets the value of a flag indicating whether the processors to be applied * to this node should be recomputed once after the execution of the current processor * ends. *

*

* This flag is usually set by processors in order to signal the fact that they have * modified the node in a way that should modify the list of processors to be applied * to it (for example, by adding an attribute which might have a processor associated). *

*

* If this flag is set to true, the engine will automatically recompute the applicable * processors before looking for the next processor to execute. *

* * @param recomputeProcessorsImmediately the new value for the flag */ public final void setRecomputeProcessorsImmediately(final boolean recomputeProcessorsImmediately) { this.recomputeProcessorsImmediately = recomputeProcessorsImmediately; } /** *

* Returns the value of a flag indicating whether there is any reason to process * this node (and its children). *

*

* This flag can be set by the engine -and also by processors- in order to avoid the * execution of certain parts of the DOM tree, both because it is known that there is * nothing to execute (and therefore it would be a waste of time trying to process it) * and also because we want to signal that specific parts of the tree should not be * executed for security reasons (for example, to avoid code injection). *

* * @return the value of the 'skippable' flag */ public final boolean isSkippable() { return this.skippable; } /** *

* Sets the value of a flag indicating whether there is any reason to process * this node (and its children). *

*

* This flag can be set by the engine -and also by processors- in order to avoid the * execution of certain parts of the DOM tree, both because it is known that there is * nothing to execute (and therefore it would be a waste of time trying to process it) * and also because we want to signal that specific parts of the tree should not be * executed for security reasons (for example, to avoid code injection). *

* * @param skippable the new value for the flag */ public final void setSkippable(final boolean skippable) { this.skippable = skippable; if (!skippable && hasParent()) { // If this node is marked as non-skippable, set its parent as // non-skippable too. if (this.parent.isSkippable()) { this.parent.setSkippable(false); } } doAdditionalSkippableComputing(skippable); } abstract void doAdditionalSkippableComputing(final boolean isSkippable); final boolean isDetached() { if (this instanceof Document) { return false; } return !hasParent(); } final boolean isPrecomputed() { return this.precomputed; } final void setPrecomputed(final boolean precomputed) { this.precomputed = precomputed; } /** *

* Returns whether the node has any node local variables * set or not. *

*

* Node local variables are variables that are set locally to a specific * node, and that will be added to the evaluation context of any * expressions --for example: OGNL or Spring EL-- executed on that node or any * of its children. *

*

* For example, the thing variable in * <div th:each="thing : ${things}">...<div> * is a node local variable that will only exist inside the <div> element * and that will be made available to any expressions executed within those limits. *

*

* Note that there is an important difference between node local variables * and node properties, as the latter are never added to the expression evaluation * context and are only meant for internally carrying around metainformation about the * DOM nodes. *

* * @return true if the node has any node local variables set, false if not */ public final boolean hasNodeLocalVariables() { return this.nodeLocalVariables != null && this.nodeLocalVariables.size() > 0; } /** *

* Returns the set of node local variable names. *

*

* Node local variables are variables that are set locally to a specific * node, and that will be added to the evaluation context of any * expressions --for example: OGNL or Spring EL-- executed on that node or any * of its children. *

*

* For example, the thing variable in * <div th:each="thing : ${things}">...<div> * is a node local variable that will only exist inside the <div> element * and that will be made available to any expressions executed within those limits. *

*

* Note that there is an important difference between node local variables * and node properties, as the latter are never added to the expression evaluation * context and are only meant for internally carrying around metainformation about the * DOM nodes. *

* * @return the set of local variable names */ public final Set getNodeLocalVariableNames() { if (this.nodeLocalVariables == null) { return Collections.emptySet(); } return this.nodeLocalVariables.keySet(); } /** *

* Returns the real inner map object containing the node local variables. This * method is only meant for internal use. DO NOT call this * method directly from your code. *

* * @return the map of node local variables */ public final Map unsafeGetNodeLocalVariables() { return this.nodeLocalVariables; } /** *

* Sets a new node local variable. *

*

* Node local variables are variables that are set locally to a specific * node, and that will be added to the evaluation context of any * expressions --for example: OGNL or Spring EL-- executed on that node or any * of its children. *

*

* For example, the thing variable in * <div th:each="thing : ${things}">...<div> * is a node local variable that will only exist inside the <div> element * and that will be made available to any expressions executed within those limits. *

*

* Note that there is an important difference between node local variables * and node properties, as the latter are never added to the expression evaluation * context and are only meant for internally carrying around metainformation about the * DOM nodes. *

* * @param name the name of the local variable * @param value the new value for the local variable */ public final void setNodeLocalVariable(final String name, final Object value) { if (this.nodeLocalVariables == null) { this.nodeLocalVariables = new HashMap(DEFAULT_NODE_LOCAL_VARIABLES_MAP_SIZE); } this.nodeLocalVariables.put(name, value); } /** *

* Sets several node local variables at once. *

*

* Node local variables are variables that are set locally to a specific * node, and that will be added to the evaluation context of any * expressions --for example: OGNL or Spring EL-- executed on that node or any * of its children. *

*

* For example, the thing variable in * <div th:each="thing : ${things}">...<div> * is a node local variable that will only exist inside the <div> element * and that will be made available to any expressions executed within those limits. *

*

* Note that there is an important difference between node local variables * and node properties, as the latter are never added to the expression evaluation * context and are only meant for internally carrying around metainformation about the * DOM nodes. *

* * @param variables the variables to be set */ public final void setAllNodeLocalVariables(final Map variables) { if (variables != null) { if (this.nodeLocalVariables == null) { this.nodeLocalVariables = new HashMap(variables); } else { this.nodeLocalVariables.putAll(variables); } } } final void unsafeSetNodeLocalVariables(final Map variables) { if (variables != null) { if (variables instanceof HashMap) { this.nodeLocalVariables = (HashMap)variables; } else { this.nodeLocalVariables = new HashMap(variables); } } else { this.nodeLocalVariables = null; } } final void precomputeNode(final Configuration configuration) { if (!isPrecomputed()) { /* * Compute the processors that are applicable to this node */ this.processors = configuration.computeProcessorsForNode(this); /* * Set skippability */ if (this.processors == null || this.processors.size() == 0) { // We only set this specific node as skippable. If we executed // "setSkippable", the whole tree would be set as skippable, which // is unnecessary due to the fact that we are going to precompute // all of this node's children in a moment. // Also, note that if any of this node's children has processors // (and therefore sets itself as "non-skippable"), it will also // set its parent as non-skippable, overriding this action. this.skippable = true; } else { // This time we execute "setSkippable" so that all parents at all // levels are also set to "false" setSkippable(false); } /* * Set the "precomputed" flag to true */ setPrecomputed(true); } /* * Let subclasses add their own preprocessing */ doAdditionalPrecomputeNode(configuration); } abstract void doAdditionalPrecomputeNode(final Configuration configuration); void processNode(final Arguments arguments, final boolean processOnlyElementNodes) { if (!this.shouldConsiderAsElementForProcessing && processOnlyElementNodes) { return; } if (this.recomputeProcessorsImmediately || this.recomputeProcessorsAfterEachExecution) { precomputeNode(arguments.getConfiguration()); this.recomputeProcessorsImmediately = false; } if (!isPrecomputed()) { precomputeNode(arguments.getConfiguration()); } if (!isSkippable()) { /* * If there are local variables at the node, add them to the ones at the * Arguments object. */ Arguments executionArguments = (this.nodeLocalVariables != null && this.nodeLocalVariables.size() > 0? arguments.addLocalVariables(this.nodeLocalVariables) : arguments); /* * If the Arguments object has local variables, synchronize the node-local * variables map. */ if (executionArguments.hasLocalVariables()) { unsafeSetNodeLocalVariables(executionArguments.getLocalVariables()); } /* * Perform the actual processing */ if (!isDetached() && this.processors != null && this.processors.size() > 0) { final IdentityCounter alreadyExecuted = new IdentityCounter(this.processors.size()); Arguments processingArguments = executionArguments; while (!isDetached() && processingArguments != null) { // This way of executing processors allows processors to perform updates // that might change which processors should be applied (for example, by // adding or removing attributes) processingArguments = applyNextProcessor(processingArguments, this, alreadyExecuted); if (processingArguments != null) { // if we didn't reach the end of processor executions, update // the Arguments object being used for processing executionArguments = processingArguments; } if (this.recomputeProcessorsImmediately || this.recomputeProcessorsAfterEachExecution) { precomputeNode(arguments.getConfiguration()); this.recomputeProcessorsImmediately = false; } } } doAdditionalProcess(executionArguments, executionArguments.getProcessOnlyElementNodes()); } } private static final Arguments applyNextProcessor(final Arguments arguments, final Node node, final IdentityCounter alreadyExecuted) { if (!node.isDetached() && node.processors != null && node.processors.size() > 0) { for (final ProcessorAndContext processor : node.processors) { if (!alreadyExecuted.isAlreadyCounted(processor)) { Arguments executionArguments = arguments; final ProcessorResult processorResult = processor.getProcessor().process(executionArguments, processor.getContext(), node); // The execution arguments need to be updated as instructed by the processor // (for example, for adding local variables) executionArguments = processorResult.computeNewArguments(executionArguments); // If we have added local variables, we should update the node's map for these variables in // order to keep them synchronized if (processorResult.hasLocalVariables() && executionArguments.hasLocalVariables()) { node.unsafeSetNodeLocalVariables(executionArguments.getLocalVariables()); } // Make sure this specific processor instance is not executed again alreadyExecuted.count(processor); return executionArguments; } } } // Either there are no processors, or all of them have already been processed return null; } abstract void doAdditionalProcess(final Arguments arguments, final boolean processOnlyElementNodes); /** *

* Creates a clone of this node. *

*

* When cloning, it can be specified whether we want to clone also the available * preprocessing information (the lists of processors that should be applicable * to each node), and also the new parent to be assigned to the DOM tree resulting from * cloning (parent can be null). *

*

* Node cloning is always performed in depth. *

* * @param newParent the new parent node of the resulting cloned DOM tree, if any (can be specified * as null). * @param cloneProcessors whether the lists of applicable processors for each node should * also be cloned or not. * @return the cloned node. */ public final Node cloneNode(final NestableNode newParent, final boolean cloneProcessors) { final Node clone = createClonedInstance(newParent, cloneProcessors); cloneNodeInternals(clone, newParent, cloneProcessors); return clone; } abstract Node createClonedInstance(final NestableNode newParent, final boolean cloneProcessors); final void cloneNodeInternals(final Node node, final NestableNode newParent, final boolean cloneProcessors) { doCloneNodeInternals(node, newParent, cloneProcessors); if (cloneProcessors) { node.processors = this.processors; node.skippable = this.skippable; node.precomputed = this.precomputed; } else { node.processors = null; node.skippable = false; node.precomputed = false; } node.parent = newParent; if (this.nodeLocalVariables != null) { node.nodeLocalVariables = new HashMap(this.nodeLocalVariables); } if (this.nodeProperties != null) { node.nodeProperties = new HashMap(this.nodeProperties); } } abstract void doCloneNodeInternals(final Node node, final NestableNode newParent, final boolean cloneProcessors); /** *

* Apply a DOM visitor. *

* * @param visitor the visitor to be executed for this node. */ public abstract void visit(final DOMVisitor visitor); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy