org.thymeleaf.engine.Attributes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of thymeleaf Show documentation
Show all versions of thymeleaf Show documentation
Modern server-side Java template engine for both web and standalone environments
/*
* =============================================================================
*
* 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.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.model.AttributeValueQuotes;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.util.FastStringWriter;
import org.thymeleaf.util.Validate;
/**
*
* @author Daniel Fernández
* @since 3.0.0
*
*/
final class Attributes {
static final String DEFAULT_WHITE_SPACE = " ";
static final String[] DEFAULT_WHITE_SPACE_ARRAY = new String[] { DEFAULT_WHITE_SPACE };
static final Attributes EMPTY_ATTRIBUTES = new Attributes(null, null);
static final Attribute[] EMPTY_ATTRIBUTE_ARRAY = new Attribute[0];
final Attribute[] attributes; // might be null if there are no attributes
final String[] innerWhiteSpaces; // might be null if there are no attributes and no whitespaces
private volatile int associatedProcessorCount = -1;
Attributes(final Attribute[] attributes, final String[] innerWhiteSpaces) {
super();
this.attributes = attributes;
this.innerWhiteSpaces = innerWhiteSpaces;
}
int getAssociatedProcessorCount() {
int c = this.associatedProcessorCount;
if (c < 0) {
this.associatedProcessorCount = c = computeAssociatedProcessorCount();
}
return c;
}
private int computeAssociatedProcessorCount() {
if (this.attributes == null || this.attributes.length == 0) {
return 0;
}
int count = 0;
int n = this.attributes.length;
while (n-- != 0) {
if (this.attributes[n].definition.hasAssociatedProcessors) {
count += this.attributes[n].definition.associatedProcessors.length;
}
}
return count;
}
private int searchAttribute(final TemplateMode templateMode, final String completeName) {
if (this.attributes == null || this.attributes.length == 0) {
return -1;
}
// We will first try exact match on the names with which the attributes appear on template, as an optimization
// on the base case (use the AttributeDefinition).
int n = this.attributes.length;
while (n-- != 0) {
if (this.attributes[n].completeName.equals(completeName)) {
return n;
}
}
// Not found that way - before discarding, let's search using AttributeDefinitions
return searchAttribute(AttributeNames.forName(templateMode, completeName));
}
private int searchAttribute(final TemplateMode templateMode, final String prefix, final String name) {
if (this.attributes == null || this.attributes.length == 0) {
return -1;
}
if (prefix == null || prefix.length() == 0) {
// Optimization: searchAttribute(name) might be faster if we are able to avoid using AttributeDefinition
return searchAttribute(templateMode, name);
}
return searchAttribute(AttributeNames.forName(templateMode, prefix, name));
}
private int searchAttribute(final AttributeName attributeName) {
if (this.attributes == null || this.attributes.length == 0) {
return -1;
}
int n = this.attributes.length;
while (n-- != 0) {
// AttributeName objects are registered in a repository and are singletons, so == is fine
if (this.attributes[n].definition.attributeName == attributeName) {
return n;
}
}
return -1;
}
boolean hasAttribute(final TemplateMode templateMode, final String completeName) {
return searchAttribute(templateMode, completeName) >= 0;
}
boolean hasAttribute(final TemplateMode templateMode, final String prefix, final String name) {
return searchAttribute(templateMode, prefix, name) >= 0;
}
boolean hasAttribute(final AttributeName attributeName) {
return searchAttribute(attributeName) >= 0;
}
Attribute getAttribute(final TemplateMode templateMode, final String completeName) {
final int pos = searchAttribute(templateMode, completeName);
if (pos < 0) {
return null;
}
return this.attributes[pos];
}
Attribute getAttribute(final TemplateMode templateMode, final String prefix, final String name) {
final int pos = searchAttribute(templateMode, prefix, name);
if (pos < 0) {
return null;
}
return this.attributes[pos];
}
Attribute getAttribute(final AttributeName attributeName) {
final int pos = searchAttribute(attributeName);
if (pos < 0) {
return null;
}
return this.attributes[pos];
}
Attribute[] getAllAttributes() {
if (this.attributes == null || this.attributes.length == 0) {
return EMPTY_ATTRIBUTE_ARRAY;
}
// We will be performing defensive cloning here. Still sleeker than returning an immutable Set or similar
return this.attributes.clone();
}
Map getAttributeMap() {
if (this.attributes == null || this.attributes.length == 0) {
return Collections.emptyMap();
}
final Map attributeMap = new LinkedHashMap(this.attributes.length + 5);
for (int i = 0; i < this.attributes.length; i++) {
attributeMap.put(this.attributes[i].completeName, this.attributes[i].value);
}
return attributeMap;
}
Attributes setAttribute(
final AttributeDefinitions attributeDefinitions, final TemplateMode templateMode,
final AttributeDefinition attributeDefinition, final String completeName,
final String value, final AttributeValueQuotes valueQuotes) {
// attributeDefinition might be null if it wasn't available at the moment of calling this method
// (including it is basically an optimization for classes that can work against engine implementations)
Validate.isTrue(value != null || templateMode != TemplateMode.XML, "Cannot set null-value attributes in XML template mode");
Validate.isTrue(valueQuotes != AttributeValueQuotes.NONE || templateMode != TemplateMode.XML, "Cannot set unquoted attributes in XML template mode");
final int existingIdx =
(attributeDefinition != null? searchAttribute(attributeDefinition.attributeName) : searchAttribute(templateMode, completeName));
if (existingIdx >= 0) {
// Attribute already exists! Must simply change its properties (might include a name case change!)
final Attribute[] newAttributes = this.attributes.clone();
newAttributes[existingIdx] =
newAttributes[existingIdx].modify(null, completeName, value, valueQuotes);
return new Attributes(newAttributes, this.innerWhiteSpaces);
}
final AttributeDefinition newAttributeDefinition =
(attributeDefinition != null ? attributeDefinition : attributeDefinitions.forName(templateMode, completeName));
final Attribute newAttribute =
new Attribute(newAttributeDefinition, completeName, null, value, valueQuotes, null, -1, -1);
final Attribute[] newAttributes;
if (this.attributes != null) {
newAttributes = new Attribute[this.attributes.length + 1];
System.arraycopy(this.attributes, 0, newAttributes, 0, this.attributes.length);
newAttributes[this.attributes.length] = newAttribute;
} else {
newAttributes = new Attribute[] { newAttribute };
}
final String[] newInnerWhiteSpaces;
if (this.innerWhiteSpaces != null) {
newInnerWhiteSpaces = new String[this.innerWhiteSpaces.length + 1];
System.arraycopy(this.innerWhiteSpaces, 0, newInnerWhiteSpaces, 0, this.innerWhiteSpaces.length);
if (this.innerWhiteSpaces.length == (this.attributes != null? this.attributes.length : 0)) {
// As many inner white spaces as attributes: no white space is being left after the last attribute
newInnerWhiteSpaces[this.innerWhiteSpaces.length] = DEFAULT_WHITE_SPACE;
} else {
// There is a white space after the last attribute, so we are going to respect it
newInnerWhiteSpaces[this.innerWhiteSpaces.length] = newInnerWhiteSpaces[this.innerWhiteSpaces.length - 1];
newInnerWhiteSpaces[this.innerWhiteSpaces.length - 1] = DEFAULT_WHITE_SPACE;
}
} else {
newInnerWhiteSpaces = DEFAULT_WHITE_SPACE_ARRAY;
}
return new Attributes(newAttributes, newInnerWhiteSpaces);
}
Attributes replaceAttribute(
final AttributeDefinitions attributeDefinitions, final TemplateMode templateMode,
final AttributeName oldName,
final AttributeDefinition newAttributeDefinition, final String newCompleteName,
final String value, final AttributeValueQuotes valueQuotes) {
// attributeDefinition might be null if it wasn't available at the moment of calling this method
// (including it is basically an optimization for classes that can work against engine implementations)
Validate.isTrue(value != null || templateMode != TemplateMode.XML, "Cannot set null-value attributes in XML template mode");
Validate.isTrue(valueQuotes != AttributeValueQuotes.NONE || templateMode != TemplateMode.XML, "Cannot set unquoted attributes in XML template mode");
if (this.attributes == null) {
return setAttribute(attributeDefinitions, templateMode, newAttributeDefinition, newCompleteName, value, valueQuotes);
}
// First check existence of the old one -- if it does not exist, this is exactly the same as setAttribute
final int oldIdx = searchAttribute(oldName);
if (oldIdx < 0) {
return setAttribute(attributeDefinitions, templateMode, newAttributeDefinition, newCompleteName, value, valueQuotes);
}
// Now check existence of the new one -- if it does exist, we will try to reuse its fields (even if the old one exists too)
int existingIdx =
(newAttributeDefinition != null? searchAttribute(newAttributeDefinition.attributeName) : searchAttribute(templateMode, newCompleteName));
if (existingIdx >= 0) {
if (oldIdx == existingIdx) {
// Old and new are the same -- this is a setAttribute
return setAttribute(attributeDefinitions, templateMode, newAttributeDefinition, newCompleteName, value, valueQuotes);
}
final Attribute[] newAttributes = new Attribute[this.attributes.length - 1];
// Do remove the old attribute
System.arraycopy(this.attributes, 0, newAttributes, 0, oldIdx);
System.arraycopy(this.attributes, oldIdx + 1, newAttributes, oldIdx, newAttributes.length - oldIdx);
// Let's compute the index of the white space to be removed. In general, we will remove the white space AFTER
int iwIdx = oldIdx + 1;
if (oldIdx + 1 == this.attributes.length) {
// We've just removed the last attribute --- in this case we prefer to remove the white space BEFORE the
// removed attribute, even if a final white space exists after this attribute
iwIdx = oldIdx;
}
final String[] newInnerWhiteSpaces = new String[this.innerWhiteSpaces.length - 1];
System.arraycopy(this.innerWhiteSpaces, 0, newInnerWhiteSpaces, 0, iwIdx);
System.arraycopy(this.innerWhiteSpaces, iwIdx + 1, newInnerWhiteSpaces, iwIdx, newInnerWhiteSpaces.length - iwIdx);
// After removing the old attribute, the position of the new one might have changed
if (existingIdx > oldIdx) {
existingIdx--;
}
// Now modify the existing new attribute directly in the new array
newAttributes[existingIdx] =
newAttributes[existingIdx].modify(null, newCompleteName, value, valueQuotes);
return new Attributes(newAttributes, newInnerWhiteSpaces);
}
// By now we know the old one exists, but the new one doesn't, so let's simply replace the old one with the new one
final AttributeDefinition computedNewAttributeDefinition =
(newAttributeDefinition != null ? newAttributeDefinition : attributeDefinitions.forName(templateMode, newCompleteName));
// We will 'modify' the old one, even if we are going to change the name, so that transition from the old name
// to the new one is smoother by keeping (if allowed) operator, value quotes, etc.
final Attribute[] newAttributes = this.attributes.clone();
newAttributes[oldIdx] =
newAttributes[oldIdx].modify(computedNewAttributeDefinition, newCompleteName, value, valueQuotes);
return new Attributes(newAttributes, this.innerWhiteSpaces);
}
Attributes removeAttribute(final TemplateMode templateMode, final String prefix, final String name) {
if (this.attributes == null) {
// We have no attribute array, nothing to remove
return this;
}
final int attrIdx = searchAttribute(templateMode, prefix, name);
if (attrIdx < 0) {
// Attribute does not exist. Just exit
return this;
}
return removeAttribute(attrIdx);
}
Attributes removeAttribute(final TemplateMode templateMode, final String completeName) {
if (this.attributes == null) {
// We have no attribute array, nothing to remove
return this;
}
final int attrIdx = searchAttribute(templateMode, completeName);
if (attrIdx < 0) {
// Attribute does not exist. Just exit
return this;
}
return removeAttribute(attrIdx);
}
Attributes removeAttribute(final AttributeName attributeName) {
if (this.attributes == null) {
// We have no attribute array, nothing to remove
return this;
}
final int attrIdx = searchAttribute(attributeName);
if (attrIdx < 0) {
// Attribute does not exist. Just exit
return this;
}
return removeAttribute(attrIdx);
}
private Attributes removeAttribute(final int attrIdx) {
if (this.attributes.length == 1 && this.innerWhiteSpaces.length == 1) {
// We are removing the last attribute and there is no extra white space: use the EMPTY constant
return EMPTY_ATTRIBUTES;
}
final Attribute[] newAttributes;
if (this.attributes.length == 1) {
newAttributes = null;
} else {
newAttributes = new Attribute[this.attributes.length - 1];
System.arraycopy(this.attributes, 0, newAttributes, 0, attrIdx);
System.arraycopy(this.attributes, attrIdx + 1, newAttributes, attrIdx, newAttributes.length - attrIdx);
}
// Let's compute the index of the white space to be removed. In general, we will remove the white space AFTER
int iwIdx = attrIdx + 1;
if (attrIdx + 1 == this.attributes.length) {
// We've just removed the last attribute --- in this case we prefer to remove the white space BEFORE the
// removed attribute, even if a final white space exists after this attribute
iwIdx = attrIdx;
}
final String[] newInnerWhiteSpaces = new String[this.innerWhiteSpaces.length - 1];
System.arraycopy(this.innerWhiteSpaces, 0, newInnerWhiteSpaces, 0, iwIdx);
System.arraycopy(this.innerWhiteSpaces, iwIdx + 1, newInnerWhiteSpaces, iwIdx, newInnerWhiteSpaces.length - iwIdx);
return new Attributes(newAttributes, newInnerWhiteSpaces);
}
void write(final Writer writer) throws IOException {
if (this.attributes == null) {
if (this.innerWhiteSpaces != null) {
// In this case, there will be only one white space
writer.write(this.innerWhiteSpaces[0]);
}
return;
}
int i = 0;
for (; i < this.attributes.length; i++) {
writer.write(this.innerWhiteSpaces[i]);
this.attributes[i].write(writer);
}
// There might be a final whitespace after the last attribute
if (i < this.innerWhiteSpaces.length) {
writer.write(this.innerWhiteSpaces[i]);
}
}
@Override
public String toString() {
final Writer stringWriter = new FastStringWriter();
try {
write(stringWriter);
} catch (final IOException e) {
throw new TemplateProcessingException("Exception processing String form of ElementAttributes", e);
}
return stringWriter.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy