/*
* =============================================================================
*
* Copyright (c) 2011-2016, 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.engine;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.thymeleaf.model.AttributeValueQuotes;
import org.thymeleaf.model.IAttribute;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.IElementProcessor;
import org.thymeleaf.processor.element.MatchingElementName;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.util.ProcessorComparators;
import org.thymeleaf.util.Validate;
/**
*
* @author Daniel Fernández
* @since 3.0.0
*
*/
abstract class AbstractProcessableElementTag
extends AbstractElementTag implements IProcessableElementTag {
private static final IElementProcessor[] EMPTY_ASSOCIATED_PROCESSORS = new IElementProcessor[0];
final Attributes attributes;
// Dialect constraints ensure anyway that we will never have duplicates here, because the same processor can
// never be applied to more than one attribute.
private volatile IElementProcessor[] associatedProcessors = null;
AbstractProcessableElementTag(
final TemplateMode templateMode,
final ElementDefinition elementDefinition,
final String elementCompleteName,
final Attributes attributes,
final boolean synthetic) {
super(templateMode, elementDefinition, elementCompleteName, synthetic);
this.attributes = attributes;
}
AbstractProcessableElementTag(
final TemplateMode templateMode,
final ElementDefinition elementDefinition,
final String elementCompleteName,
final Attributes attributes,
final boolean synthetic,
final String templateName,
final int line,
final int col) {
super(templateMode, elementDefinition, elementCompleteName, synthetic, templateName, line, col);
this.attributes = attributes;
}
public final boolean hasAttribute(final String completeName) {
Validate.notNull(completeName, "Attribute name cannot be null");
if (this.attributes == null) {
return false;
}
return this.attributes.hasAttribute(this.templateMode, completeName);
}
public final boolean hasAttribute(final String prefix, final String name) {
Validate.notNull(name, "Attribute name cannot be null");
if (this.attributes == null) {
return false;
}
return this.attributes.hasAttribute(this.templateMode, prefix, name);
}
public final boolean hasAttribute(final AttributeName attributeName) {
Validate.notNull(attributeName, "Attribute name cannot be null");
if (this.attributes == null) {
return false;
}
return this.attributes.hasAttribute(attributeName);
}
public final IAttribute getAttribute(final String completeName) {
Validate.notNull(completeName, "Attribute name cannot be null");
if (this.attributes == null) {
return null;
}
return this.attributes.getAttribute(this.templateMode, completeName);
}
public final IAttribute getAttribute(final String prefix, final String name) {
Validate.notNull(name, "Attribute name cannot be null");
if (this.attributes == null) {
return null;
}
return this.attributes.getAttribute(this.templateMode, prefix, name);
}
public final IAttribute getAttribute(final AttributeName attributeName) {
Validate.notNull(attributeName, "Attribute name cannot be null");
if (this.attributes == null) {
return null;
}
return this.attributes.getAttribute(attributeName);
}
public final String getAttributeValue(final String completeName) {
Validate.notNull(completeName, "Attribute name cannot be null");
if (this.attributes == null) {
return null;
}
final Attribute attribute = this.attributes.getAttribute(this.templateMode, completeName);
return attribute != null? attribute.getValue() : null;
}
public final String getAttributeValue(final String prefix, final String name) {
Validate.notNull(name, "Attribute name cannot be null");
if (this.attributes == null) {
return null;
}
final Attribute attribute = this.attributes.getAttribute(this.templateMode, prefix, name);
return attribute != null? attribute.getValue() : null;
}
public final String getAttributeValue(final AttributeName attributeName) {
Validate.notNull(attributeName, "Attribute name cannot be null");
if (this.attributes == null) {
return null;
}
final Attribute attribute = this.attributes.getAttribute(attributeName);
return attribute != null? attribute.getValue() : null;
}
public IAttribute[] getAllAttributes() {
if (this.attributes == null) {
return Attributes.EMPTY_ATTRIBUTE_ARRAY;
}
return this.attributes.getAllAttributes();
}
public Map<String,String> getAttributeMap() {
if (this.attributes == null) {
return Collections.emptyMap();
}
return this.attributes.getAttributeMap();
}
IElementProcessor[] getAssociatedProcessors() {
IElementProcessor[] p = this.associatedProcessors;
if (p == null) {
this.associatedProcessors = p = computeProcessors();
}
return p;
}
boolean hasAssociatedProcessors() {
return getAssociatedProcessors().length > 0;
}
private IElementProcessor[] computeProcessors() {
final int associatedProcessorCount = (this.attributes != null? this.attributes.getAssociatedProcessorCount() : 0);
// If there are no processors associated with attributes, this is much easier
if (this.attributes == null || associatedProcessorCount == 0) {
return (this.elementDefinition.hasAssociatedProcessors? this.elementDefinition.associatedProcessors : EMPTY_ASSOCIATED_PROCESSORS);
}
// At this point we know for sure there are processors associated with attributes
final int elementProcessorCount =
(this.elementDefinition.hasAssociatedProcessors? this.elementDefinition.associatedProcessors.length : 0);
IElementProcessor[] processors = new IElementProcessor[elementProcessorCount + associatedProcessorCount];
if (elementProcessorCount > 0) {
System.arraycopy(this.elementDefinition.associatedProcessors, 0, processors, 0, elementProcessorCount);
}
int idx = elementProcessorCount;
int n = this.attributes.attributes.length;
while (n-- != 0) {
if (!this.attributes.attributes[n].definition.hasAssociatedProcessors) {
continue;
}
final IElementProcessor[] attributeAssociatedProcessors = this.attributes.attributes[n].definition.associatedProcessors;
for (int i = 0; i < attributeAssociatedProcessors.length; i++) {
// We should never have duplicates. The same attribute can never appear twice in an element (parser
// restrictions + the way this class's 'setAttribute' works), plus a specific processor instance can
// never appear in more than one dialect, nor be applied to more than one attribute name.
// Now for each processor, before adding it to the list, we must first determine whether it requires
// a specific element name and, if so, confirm that it is the same as the name of the element these
// attributes live at.
final MatchingElementName matchingElementName = attributeAssociatedProcessors[i].getMatchingElementName();
if (matchingElementName != null && !matchingElementName.matches(this.elementDefinition.elementName)) {
continue;
}
// Just add the processor to the list
processors[idx++] = attributeAssociatedProcessors[i];
}
}
// At the end in some (very few) cases we might have a mismatch because some attribute processors didn't apply
// due to the element name not matching. In such cases, we need to readjust the array size.
if (idx < processors.length) {
processors = Arrays.copyOf(processors, idx);
}
if (processors.length > 1) {
Arrays.sort(processors, ProcessorComparators.PROCESSOR_COMPARATOR);
}
return processors;
}
abstract AbstractProcessableElementTag setAttribute(
final AttributeDefinitions attributeDefinitions,
final AttributeDefinition attributeDefinition, final String completeName,
final String value, final AttributeValueQuotes valueQuotes);
abstract AbstractProcessableElementTag replaceAttribute(
final AttributeDefinitions attributeDefinitions,
final AttributeName oldName, final AttributeDefinition newAttributeDefinition, final String completeNewName,
final String value, final AttributeValueQuotes valueQuotes);
abstract AbstractProcessableElementTag removeAttribute(final String prefix, final String name);
abstract AbstractProcessableElementTag removeAttribute(final String completeName);
abstract AbstractProcessableElementTag removeAttribute(final AttributeName attributeName);
}