/*
* =============================================================================
*
* 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.Arrays;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.model.ICDATASection;
import org.thymeleaf.model.ICloseElementTag;
import org.thymeleaf.model.IComment;
import org.thymeleaf.model.IDocType;
import org.thymeleaf.model.IModel;
import org.thymeleaf.model.IModelVisitor;
import org.thymeleaf.model.IOpenElementTag;
import org.thymeleaf.model.IProcessingInstruction;
import org.thymeleaf.model.IStandaloneElementTag;
import org.thymeleaf.model.ITemplateEnd;
import org.thymeleaf.model.ITemplateEvent;
import org.thymeleaf.model.ITemplateStart;
import org.thymeleaf.model.IText;
import org.thymeleaf.model.IXMLDeclaration;
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 Model implements IModel {
private static final int INITIAL_EVENT_QUEUE_SIZE = 50; // 50 events by default, will auto-grow
private IEngineConfiguration configuration;
private TemplateMode templateMode;
IEngineTemplateEvent[] queue;
int queueSize;
Model(final IEngineConfiguration configuration, final TemplateMode templateMode) {
super();
Validate.notNull(configuration, "Engine Configuration cannot be null");
Validate.notNull(templateMode, "Template Mode cannot be null");
this.templateMode = templateMode;
this.configuration = configuration;
this.queue = new IEngineTemplateEvent[INITIAL_EVENT_QUEUE_SIZE];
Arrays.fill(this.queue, null);
this.queueSize = 0;
}
Model(final IModel model) {
super();
Validate.notNull(model, "Model cannot be null");
this.configuration = model.getConfiguration();
this.templateMode = model.getTemplateMode();
if (model instanceof Model) {
final Model mmodel = (Model) model;
this.queue = mmodel.queue.clone();
this.queueSize = mmodel.queueSize;
} else if (model instanceof TemplateModel) {
final TemplateModel templateModel = (TemplateModel) model;
this.queue = new IEngineTemplateEvent[templateModel.queue.length + INITIAL_EVENT_QUEUE_SIZE/2];
System.arraycopy(templateModel.queue, 1, this.queue, 0, templateModel.queue.length - 2);
this.queueSize = templateModel.queue.length - 2;
} else {
this.queue = new IEngineTemplateEvent[INITIAL_EVENT_QUEUE_SIZE];
Arrays.fill(this.queue, null);
this.queueSize = 0;
insertModel(0, model);
}
}
public final IEngineConfiguration getConfiguration() {
return this.configuration;
}
public final TemplateMode getTemplateMode() {
return this.templateMode;
}
public int size() {
return this.queueSize;
}
public ITemplateEvent get(final int pos) {
return this.queue[pos];
}
public void add(final ITemplateEvent event) {
insert(this.queueSize, event);
}
public void insert(final int pos, final ITemplateEvent event) {
if (event == null) {
return;
}
final IEngineTemplateEvent engineEvent = asEngineEvent(event);
// Check that the event that is going to be inserted is not a template start/end
if (engineEvent == TemplateStart.TEMPLATE_START_INSTANCE || engineEvent == TemplateEnd.TEMPLATE_END_INSTANCE) {
throw new TemplateProcessingException(
"Cannot insert event of type TemplateStart/TemplateEnd. These " +
"events can only be added to models internally during template parsing.");
}
// Check there is room for a new event, or grow the queue if not
if (this.queue.length == this.queueSize) {
this.queue = Arrays.copyOf(this.queue, this.queue.length + INITIAL_EVENT_QUEUE_SIZE/2);
}
// Make room for the new event
if (pos != this.queueSize) {
System.arraycopy(this.queue, pos, this.queue, pos + 1, this.queueSize - pos);
}
// Set the new event in its new position
this.queue[pos] = engineEvent;
this.queueSize++;
}
public void replace(final int pos, final ITemplateEvent event) {
if (event == null) {
return;
}
final IEngineTemplateEvent engineEvent = asEngineEvent(event);
// Check that the event that is going to be inserted is not a template start/end
if (engineEvent == TemplateStart.TEMPLATE_START_INSTANCE || engineEvent == TemplateEnd.TEMPLATE_END_INSTANCE) {
throw new TemplateProcessingException(
"Cannot insert event of type TemplateStart/TemplateEnd. These " +
"events can only be added to models internally during template parsing.");
}
// Set the new event in its new position
this.queue[pos] = engineEvent;
}
public void addModel(final IModel model) {
insertModel(this.queueSize, model);
}
public void insertModel(final int pos, final IModel model) {
if (model == null || model.size() == 0) {
return;
}
if (this.configuration != model.getConfiguration()) {
throw new TemplateProcessingException(
"Cannot add model of class " + model.getClass().getName() + " to the current template, as " +
"it was created using a different Template Engine Configuration.");
}
if (this.templateMode != model.getTemplateMode()) {
throw new TemplateProcessingException(
"Cannot add model of class " + model.getClass().getName() + " to the current template, as " +
"it was created using a different Template Mode: " + model.getTemplateMode() + " instead of " +
"the current " + this.templateMode);
}
if (this.queue.length <= (this.queueSize + model.size())) {
// We need to grow the queue!
this.queue = Arrays.copyOf(this.queue, Math.max(this.queueSize + model.size(), this.queue.length + INITIAL_EVENT_QUEUE_SIZE/2));
}
if (model instanceof TemplateModel) {
doInsertTemplateModel(pos, (TemplateModel)model);
} else if (model instanceof Model) {
doInsertModel(pos, (Model)model);
} else {
doInsertOtherModel(pos, model);
}
}
private void doInsertModel(final int pos, final Model model) {
// Make room for the new events (if necessary because pos < this.queueSize)
System.arraycopy(this.queue, pos, this.queue, pos + model.queueSize, this.queueSize - pos);
// Copy the new events to their new position
System.arraycopy(model.queue, 0, this.queue, pos, model.queueSize);
this.queueSize += model.queueSize;
}
private void doInsertTemplateModel(final int pos, final TemplateModel model) {
// We compute the insertion size by subtracting the TemplateStart/TemplateEnd events
final int insertionSize = model.queue.length - 2;
// Make room for the new events (if necessary because pos < this.queueSize)
System.arraycopy(this.queue, pos, this.queue, pos + insertionSize, this.queueSize - pos);
// Copy the new events to their new position
System.arraycopy(model.queue, 1, this.queue, pos, insertionSize);
this.queueSize += insertionSize;
}
private void doInsertOtherModel(final int pos, final IModel model) {
// We know nothing about this model implementation, so we will use the public interface methods
final int modelSize = model.size();
for (int i = 0; i < modelSize; i++) {
insert(pos + i, model.get(i));
}
}
public void remove(final int pos) {
System.arraycopy(this.queue, pos + 1, this.queue, pos, this.queueSize - (pos + 1));
this.queueSize--;
}
public void reset() {
this.queueSize = 0;
}
void process(final ITemplateHandler handler) {
for (int i = 0; i < this.queueSize; i++) {
this.queue[i].beHandled(handler);
}
}
int process(final ITemplateHandler handler, final int offset, final TemplateFlowController controller) {
if (controller == null) {
process(handler);
return this.queueSize;
}
if (this.queueSize == 0 || offset >= this.queueSize) {
return 0;
}
int i = offset;
while (i < this.queueSize && !controller.stopProcessing) {
this.queue[i++].beHandled(handler);
}
return (i - offset);
}
public IModel cloneModel() {
return new Model(this);
}
void resetAsCloneOf(final Model model) {
this.configuration = model.configuration;
this.templateMode = model.templateMode;
if (this.queue.length < model.queueSize) {
this.queue = new IEngineTemplateEvent[model.queueSize];
}
System.arraycopy(model.queue, 0, this.queue, 0, model.queueSize);
this.queueSize = model.queueSize;
}
public final void write(final Writer writer) throws IOException {
for (int i = 0; i < this.queueSize; i++) {
this.queue[i].write(writer);
}
}
public void accept(final IModelVisitor visitor) {
for (int i = 0; i < this.queueSize; i++) {
// We will execute the visitor on the Immutable events, that we need to create during the visit
this.queue[i].accept(visitor);
}
}
// Note we will use object equality for comparing events here - the idea is to check whether
// a model has been changed at all, and replacing an event with an equivalent one would be
// considered "a change" anyway.
boolean sameAs(final Model model) {
if (model == null || model.queueSize != this.queueSize) {
return false;
}
for (int i = 0; i < this.queueSize; i++) {
if (this.queue[i] != model.queue[i]) {
return false;
}
}
return true;
}
@Override
public final String toString() {
try {
final Writer writer = new FastStringWriter();
write(writer);
return writer.toString();
} catch (final IOException e) {
throw new TemplateProcessingException(
"Error while creating String representation of model");
}
}
static IEngineTemplateEvent asEngineEvent(final ITemplateEvent event) {
if (event instanceof IEngineTemplateEvent) {
return (IEngineTemplateEvent)event;
}
if (event instanceof IText) {
return Text.asEngineText((IText) event);
}
if (event instanceof IOpenElementTag) {
return OpenElementTag.asEngineOpenElementTag((IOpenElementTag) event);
}
if (event instanceof ICloseElementTag) {
return CloseElementTag.asEngineCloseElementTag((ICloseElementTag) event);
}
if (event instanceof IStandaloneElementTag) {
return StandaloneElementTag.asEngineStandaloneElementTag((IStandaloneElementTag) event);
}
if (event instanceof IDocType) {
return DocType.asEngineDocType((IDocType) event);
}
if (event instanceof IComment) {
return Comment.asEngineComment((IComment) event);
}
if (event instanceof ICDATASection) {
return CDATASection.asEngineCDATASection((ICDATASection) event);
}
if (event instanceof IXMLDeclaration) {
return XMLDeclaration.asEngineXMLDeclaration((IXMLDeclaration) event);
}
if (event instanceof IProcessingInstruction) {
return ProcessingInstruction.asEngineProcessingInstruction((IProcessingInstruction) event);
}
if (event instanceof ITemplateStart) {
return TemplateStart.asEngineTemplateStart((ITemplateStart) event);
}
if (event instanceof ITemplateEnd) {
return TemplateEnd.asEngineTemplateEnd((ITemplateEnd) event);
}
throw new TemplateProcessingException(
"Cannot handle in event of type: " + event.getClass().getName());
}
}