blob: 7d372fccccfaf85e930b30ce70ea8cb1ff24db8a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005-2014 Obeo
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.query.legacy.gen.template;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.sirius.query.legacy.ecore.factories.EFactory;
import org.eclipse.sirius.query.legacy.ecore.factories.FactoryException;
import org.eclipse.sirius.query.legacy.gen.AcceleoEcoreGenPlugin;
import org.eclipse.sirius.query.legacy.gen.AcceleoGenMessages;
import org.eclipse.sirius.query.legacy.gen.template.eval.ENode;
import org.eclipse.sirius.query.legacy.gen.template.eval.ENodeCastException;
import org.eclipse.sirius.query.legacy.gen.template.eval.ENodeException;
import org.eclipse.sirius.query.legacy.gen.template.eval.LaunchManager;
import org.eclipse.sirius.query.legacy.gen.template.eval.TextModelMapping;
import org.eclipse.sirius.query.legacy.gen.template.expressions.TemplateExpression;
import org.eclipse.sirius.query.legacy.gen.template.scripts.IScript;
import org.eclipse.sirius.query.legacy.gen.template.scripts.ScriptDescriptor;
import org.eclipse.sirius.query.legacy.gen.template.statements.TemplateCommentStatement;
import org.eclipse.sirius.query.legacy.gen.template.statements.TemplateFeatureStatement;
import org.eclipse.sirius.query.legacy.gen.template.statements.TemplateForStatement;
import org.eclipse.sirius.query.legacy.gen.template.statements.TemplateIfStatement;
import org.eclipse.sirius.query.legacy.tools.strings.Int2;
import org.eclipse.sirius.query.legacy.tools.strings.TextSearch;
/**
* This is a template for the generation tool. This class provides services such
* as :
* <ul>
* <li>Simple template initialization methods.
* <li>Functions to apply the template to model's objects to allow dynamic
* generation.
* </ul>
*
*
*/
public class Template extends TemplateNodeElement {
/**
* Empty template reference.
*/
public static final Template EMPTY = new Template(null);
/**
* The descriptor of this template.
*/
protected ScriptDescriptor descriptor = null;
/**
* The post expression.
*/
protected TemplateExpression postExpression = null;
/**
* Indicates if the last evaluation returned an empty node.
*/
protected boolean emptyEvaluation = true;
/**
* Nodes of the template.
*/
protected List elements = new ArrayList();
/**
* Constructor.
*
* @param script
* is the script
*/
public Template(IScript script) {
super(script);
}
/**
* @return the descriptor of this template
*/
public ScriptDescriptor getDescriptor() {
return descriptor;
}
/**
* @param descriptor
* is the descriptor of this template
*/
public void setDescriptor(ScriptDescriptor descriptor) {
this.descriptor = descriptor;
}
/**
* @return the post expression
*/
public TemplateExpression getPostExpression() {
return postExpression;
}
/**
* @param postExpression
* is the post expression
*/
public void setPostExpression(TemplateExpression postExpression) {
this.postExpression = postExpression;
}
/**
* Indicates if the last evaluation returned an empty node.
*
* @return true if the last evaluation returned an empty node
*/
public boolean isEmptyEvaluation() {
return emptyEvaluation;
}
/**
* It saves template's state.
*
* @return the template's state
*/
public Object copyState() {
return new ArrayList(elements);
}
/**
* It restores template's state.
*
* @param state
* is the template's state to be restored
*/
public void fromState(Object state) {
elements = (List) state;
}
/**
* Removes all of the elements from this template.
*/
public void clear() {
elements.clear();
}
/**
* Appends the specified element to the end of this template.
*
* @param element
* is the element to be added
*/
public void append(TemplateNodeElement element) {
if (element instanceof TemplateText) {
if (((TemplateText) element).text.length() > 0) {
elements.add(element);
element.setParent(this);
}
} else if (element != null) {
elements.add(element);
element.setParent(this);
}
}
/* (non-Javadoc) */
@Override
public String toString() {
final StringBuffer text = new StringBuffer(""); //$NON-NLS-1$
final Iterator it = elements.iterator();
while (it.hasNext()) {
final TemplateNodeElement element = (TemplateNodeElement) it.next();
text.append(element.toString());
}
return text.toString();
}
/* (non-Javadoc) */
@Override
public String getOutlineText() {
return ""; //$NON-NLS-1$
}
/**
* It creates a dynamic template for the given text.
*
* @param buffer
* is the textual representation of the new template
* @param script
* is the generator's configuration used to apply the new
* template to model's objects
* @param object
* is the object to select when an error is detected
* @return the new template
* @throws ENodeException
*/
public static Template from(String buffer, IScript script, EObject object) throws ENodeException {
try {
return Template.read(buffer, script);
} catch (final TemplateSyntaxException e) {
throw new ENodeException(e.getMessage(), e.getPos(), script, object, true);
}
}
/**
* It checks the syntax of the new template text and creates a template for
* the given text.
*
* @param buffer
* is the textual representation of the new template
* @param script
* is the generator's configuration used to apply the new
* template to model's objects
* @return the new template
* @throws TemplateSyntaxException
*/
private static Template read(String buffer, IScript script) throws TemplateSyntaxException {
return Template.read(buffer, new Int2(0, buffer.length()), script);
}
/**
* It checks the syntax and creates a template for the given part of the
* text. The part of the text to be parsed is delimited by the given limits.
*
* @param buffer
* is the textual representation of the script that contains
* templates
* @param limits
* delimits the part of the text to be parsed for this template
* @param script
* is the generator's configuration used to apply the new
* template to model's objects
* @return the new template
* @throws TemplateSyntaxException
*/
public static Template read(String buffer, Int2 limits, IScript script) throws TemplateSyntaxException {
Template template = (Template) Template.readPreferred("read", buffer, limits, script); //$NON-NLS-1$
if (template == null) {
template = new Template(script);
template.setPos(limits);
if (buffer != null) {
final int[] pos = new int[4];
for (int i = 0; i < pos.length; i++) {
pos[i] = -2;
}
int i = limits.b();
while (i < limits.e()) {
if (pos[0] != -1 && i > pos[0]) {
pos[0] = TextSearch.getDefaultSearch().indexIn(buffer, TemplateConstants.COMMENT_BEGIN, i, limits.e()).b();
}
if (pos[1] != -1 && i > pos[1]) {
pos[1] = TextSearch.getDefaultSearch().indexIn(buffer, TemplateConstants.IF_BEGIN, i, limits.e()).b();
}
if (pos[2] != -1 && i > pos[2]) {
pos[2] = TextSearch.getDefaultSearch().indexIn(buffer, TemplateConstants.FOR_BEGIN, i, limits.e()).b();
}
if (pos[3] != -1 && i > pos[3]) {
pos[3] = TextSearch.getDefaultSearch().indexIn(buffer, TemplateConstants.FEATURE_BEGIN, i, limits.e()).b();
}
final int iTab = Template.indexOfMin(pos);
if (iTab == 0) {
i = Template.readStatement(buffer, TemplateConstants.COMMENT_BEGIN, TemplateConstants.COMMENT_END, new Int2(i, limits.e()), template, script);
} else if (iTab == 1) {
i = Template.readStatement(buffer, TemplateConstants.IF_BEGIN, TemplateConstants.IF_END, new Int2(i, limits.e()), template, script);
} else if (iTab == 2) {
i = Template.readStatement(buffer, TemplateConstants.FOR_BEGIN, TemplateConstants.FOR_END, new Int2(i, limits.e()), template, script);
} else if (iTab == 3) {
i = Template.readStatement(buffer, TemplateConstants.FEATURE_BEGIN, TemplateConstants.FEATURE_END, new Int2(i, limits.e()), template, script);
} else { // -1
final Int2 posText = new Int2(i, limits.e());
template.append(Template.readMiddleElement(buffer, posText, script));
i = limits.e();
}
}
}
}
return template;
}
/**
* It checks the syntax and creates a "middle" template element for the
* given part of the text. The part of the text to be parsed is delimited by
* the given limits. A middle template element is a textual representation
* that doesn't contain "<%...%>".
*
* @param buffer
* is the textual representation of the script that contains
* templates
* @param limits
* delimits the part of the text to be parsed for this "middle"
* template element
* @param script
* is the generator's configuration used to apply the new
* template element to model's objects
* @return the new "middle" template element
* @throws TemplateSyntaxException
*/
public static TemplateNodeElement readMiddleElement(String buffer, Int2 limits, IScript script) throws TemplateSyntaxException {
TemplateNodeElement element = Template.readPreferred("readMiddleElement", buffer, limits, script); //$NON-NLS-1$
if (element == null) {
element = new TemplateText(buffer.substring(limits.b(), limits.e()), script);
element.setPos(limits);
}
return element;
}
/**
* It checks the syntax and creates a template element for the given part of
* the text. The part of the text to be parsed is delimited by the given
* limits. It tries to call a static method on the preferred template
* before. The static attribute 'preferredTemplate' has to be put before.
*
* @param method
* is the method name
* @param buffer
* is the textual representation of the script that contains
* templates
* @param limits
* delimits the part of the text to be parsed
* @param script
* is the generator's configuration used to apply the new
* template element to model's objects
* @return the new template element
* @throws TemplateSyntaxException
*/
private static TemplateNodeElement readPreferred(String method, String buffer, Int2 limits, IScript script) throws TemplateSyntaxException {
if (Template.preferredTemplate != Template.class) {
try {
return (TemplateNodeElement) Template.preferredTemplate.getMethod(method, new Class[] { String.class, Int2.class, IScript.class }).invoke(Template.preferredTemplate,
new Object[] { buffer, limits, script });
} catch (final IllegalArgumentException e) {
AcceleoEcoreGenPlugin.getDefault().log(e, true);
} catch (final SecurityException e) {
AcceleoEcoreGenPlugin.getDefault().log(e, true);
} catch (final IllegalAccessException e) {
AcceleoEcoreGenPlugin.getDefault().log(e, true);
} catch (final InvocationTargetException e) {
if (e.getTargetException() instanceof TemplateSyntaxException) {
throw (TemplateSyntaxException) e.getTargetException();
} else {
AcceleoEcoreGenPlugin.getDefault().log(e, true);
}
} catch (final NoSuchMethodException e) {
AcceleoEcoreGenPlugin.getDefault().log(e, true);
}
}
return null;
}
/**
* The preferred template is the class to use to create the templates in the
* sub statements.
*/
protected static Class preferredTemplate = Template.class;
/**
* Gets the index of the minimum integer in the given table.
*
* @param pos
* is the table
* @return the index of the minimum integer
*/
protected static int indexOfMin(int[] pos) {
int index = -1;
int min = Integer.MAX_VALUE;
for (int i = 0; i < pos.length; i++) {
if (pos[i] > -1 && pos[i] < min) {
index = i;
min = pos[i];
}
}
return index;
}
/**
* Reads the statements in the buffer, starting at a specified position, and
* returns the ending index.
*
* @param buffer
* is the textual representation of the script that contains
* templates
* @param tagBegin
* is the begin tag of the statement
* @param tagEnd
* is the end tag of the statement
* @param limits
* delimits the part of the text to be parsed
* @param template
* is the current template
* @param script
* is the generator's configuration used to apply the new
* statement to model's objects
* @return the ending index, or -1
* @throws TemplateSyntaxException
*/
protected static int readStatement(String buffer, String tagBegin, String tagEnd, Int2 limits, Template template, IScript script) throws TemplateSyntaxException {
Int2 end = Int2.NOT_FOUND;
final Int2 begin = TextSearch.getDefaultSearch().indexIn(buffer, tagBegin, limits.b(), limits.e());
// ASSERT (begin.b > -1)
if (tagBegin == TemplateConstants.COMMENT_BEGIN) {
end = TextSearch.getDefaultSearch().blockIndexEndIn(buffer, tagBegin, tagEnd, begin.b(), limits.e(), false);
} else {
end = TextSearch.getDefaultSearch().blockIndexEndIn(buffer, tagBegin, tagEnd, begin.b(), limits.e(), true, null, TemplateConstants.INHIBS_STATEMENT);
}
if (end.b() > -1) {
final boolean untab = tagBegin != TemplateConstants.FEATURE_BEGIN && Template.isFirstSignificantOfLine(buffer, begin.b()) && Template.isLastSignificantOfLine(buffer, end.e());
if (begin.b() > limits.b()) {
int iEndText = begin.b();
if (untab) {
// Delete (\s|\t)* before <%if..., <%for...
int iPrevLine = TextSearch.getDefaultSearch().lastIndexIn(buffer, "\n", limits.b(), begin.b()).e(); //$NON-NLS-1$
if (iPrevLine == -1) {
iPrevLine = limits.b();
}
if (buffer.substring(iPrevLine, begin.b()).trim().length() == 0) {
iEndText = iPrevLine;
}
}
final Int2 posText = new Int2(limits.b(), iEndText);
template.append(Template.readMiddleElement(buffer, posText, script));
}
if (tagBegin == TemplateConstants.IF_BEGIN) {
template.append(TemplateIfStatement.fromString(buffer, new Int2(begin.e(), end.b()), script));
}
if (tagBegin == TemplateConstants.FOR_BEGIN) {
template.append(TemplateForStatement.fromString(buffer, new Int2(begin.e(), end.b()), script));
}
if (tagBegin == TemplateConstants.FEATURE_BEGIN) {
template.append(TemplateFeatureStatement.fromString(buffer, new Int2(begin.e(), end.b()), script));
}
if (tagBegin == TemplateConstants.COMMENT_BEGIN) {
template.append(TemplateCommentStatement.fromString(buffer, new Int2(begin.e(), end.b()), script));
}
if (untab) {
// Delete (\s|\t)*\n after <%}%>
int iNextLine = TextSearch.getDefaultSearch().indexIn(buffer, "\n", end.e(), limits.e()).e(); //$NON-NLS-1$
if (iNextLine == -1) {
iNextLine = limits.e();
}
if (buffer.substring(end.e(), iNextLine).trim().length() == 0) {
return iNextLine;
}
}
return end.e();
} else {
template.append(Template.readMiddleElement(buffer, limits, script));
throw new TemplateSyntaxException(AcceleoGenMessages.getString("Template.UnclosedTag", new Object[] { tagEnd, tagBegin, }), script, new Int2(begin.b(), limits.e())); //$NON-NLS-1$
}
}
private static boolean isFirstSignificantOfLine(String buffer, int index) {
if (index > 0 && index < buffer.length()) {
index--;
while (index >= 0) {
final char c = buffer.charAt(index);
if (c == '\n') {
return true;
} else if (c == ' ' || c == '\t' || c == '\r') {
index--;
} else {
return false;
}
}
}
return true;
}
private static boolean isLastSignificantOfLine(String buffer, int index) {
if (index >= 0) {
while (index < buffer.length()) {
final char c = buffer.charAt(index);
if (c == '\n') {
return true;
} else if (c == ' ' || c == '\t' || c == '\r') {
index++;
} else {
return false;
}
}
}
return true;
}
/**
* Formats the text of a template in a script.
*
* @param buffer
* is the full text of a script
* @param limits
* delimits the part of the text to parse for this template
* @param nbReturn
* is the number of return character to ignore at the end of the
* template
* @return is the new limits that delimits the part of the text to parse for
* this template
*/
public static Int2 formatTemplate(String buffer, Int2 limits, int nbReturn) {
if (limits.b() == -1 || limits.b() > limits.e()) {
return Int2.NOT_FOUND;
}
final String text = buffer.substring(limits.b(), limits.e());
// Search first valid char
int posBegin = text.indexOf("\n"); //$NON-NLS-1$
if (posBegin == -1 || text.substring(0, posBegin).trim().length() > 0) {
posBegin = 0;
} else {
posBegin++;
}
// Search last valid char
if (text.length() > 0) {
int n = 0;
int endLine = text.length();
int posEnd = text.length() - 1;
boolean stop = false;
while (!stop) {
final char c = text.charAt(posEnd);
if (nbReturn == 0 && c == '\n') {
posEnd++;
stop = true;
} else if (c == '\n' && n < nbReturn) {
n++;
endLine = posEnd;
} else if (c == '\r') {
endLine = posEnd;
} else if (c == ' ' || c == '\t') {
} else {
// Keep space at end
posEnd = endLine;
stop = true;
}
if (!stop) {
if (posEnd > posBegin) {
posEnd--;
} else {
if (nbReturn != 0) {
posEnd = endLine;
}
stop = true;
}
}
}
if (posEnd >= posBegin) {
boolean formatWithComment = false;
String sub = text.substring(posBegin, posEnd);
if (sub.startsWith(TemplateConstants.COMMENT_BEGIN) && sub.indexOf(TemplateConstants.COMMENT_END) > -1) {
posBegin = text.indexOf(TemplateConstants.COMMENT_END, posBegin) + TemplateConstants.COMMENT_END.length();
formatWithComment = true;
if (posEnd > posBegin) {
sub = sub.substring(posBegin);
} else {
sub = ""; //$NON-NLS-1$
}
}
if (sub.endsWith(TemplateConstants.COMMENT_END) && sub.lastIndexOf(TemplateConstants.COMMENT_BEGIN, posEnd) > -1) {
posEnd = text.lastIndexOf(TemplateConstants.COMMENT_BEGIN, posEnd);
formatWithComment = true;
}
if (formatWithComment) {
return Template.formatTemplate(buffer, new Int2(limits.b() + posBegin, limits.b() + posEnd), nbReturn);
} else {
return new Int2(limits.b() + posBegin, limits.b() + posEnd);
}
}
}
return limits;
}
/**
* Evaluates this template on an EObject, and converts the result to a
* string.
*
* @param object
* is the model's object
* @param mode
* is the mode in which to launch, one of the mode constants
* defined - RUN_MODE or DEBUG_MODE
* @return the textual representation of the EObject for this template
* @throws ENodeException
* @throws FactoryException
*/
public String evaluateAsString(EObject object, LaunchManager mode) throws ENodeException, FactoryException {
final String text = evaluate(object, mode).asString();
return text;
}
/**
* Evaluates this template on an EObject. The result node of generation is
* an ENode. The given comment is generated before the object if it isn't
* null or empty.
*
* @param object
* is the model's object
* @param comment
* that is generated before the object if it isn't null or empty
* @param mode
* is the mode in which to launch, one of the mode constants
* defined - RUN_MODE or DEBUG_MODE
* @return the result node of generation
* @throws ENodeException
* @throws FactoryException
* @see ENode
*/
public ENode evaluateWithComment(EObject object, LaunchManager mode) throws ENodeException, FactoryException {
final ENode node = new ENode(ENode.EMPTY, object, this, mode.isSynchronize());
String comment = eGetAsString(object, "comment"); //$NON-NLS-1$
if (comment.length() > 0) {
node.append(comment, TextModelMapping.HIGHLIGHTED_COMMENT);
}
node.append(evaluate(object, mode));
comment = eGetAsString(object, "endLineComment"); //$NON-NLS-1$
if (comment.length() > 0) {
node.append(comment, TextModelMapping.HIGHLIGHTED_COMMENT);
}
return node;
}
private String eGetAsString(EObject object, String feature) {
try {
String result = EFactory.eGetAsString(object, feature);
if (result == null) {
result = ""; //$NON-NLS-1$
}
return result;
} catch (final FactoryException e) {
return ""; //$NON-NLS-1$
}
}
/* (non-Javadoc) */
@Override
public ENode evaluate(EObject object, LaunchManager mode) throws ENodeException, FactoryException {
if (mode.getMonitor() != null && mode.getMonitor().isCanceled()) {
throw new OperationCanceledException();
}
ENode node = new ENode(ENode.EMPTY, object, this, mode.isSynchronize());
boolean parentIsStatement = getParent() instanceof TemplateForStatement || getParent() instanceof TemplateIfStatement;
if (!parentIsStatement && currentEval.contains(object)) {
emptyEvaluation = true;
throw new ENodeException(AcceleoGenMessages.getString("Template.RecursiveCall", new Object[] { toString(), }), pos, script, object, true); //$NON-NLS-1$
} else {
if (script != null) {
final ENode tmp = new ENode(object, this, mode.isSynchronize());
script.contextPush(IScript.CURRENT_NODE, tmp);
script.contextPush(IScript.TEMPLATE_NODE, tmp);
}
try {
if (!parentIsStatement) {
currentEval.add(object);
}
TemplateNodeElement precedingElement = null;
ENode precedingChild = null;
final Iterator elements = this.elements.iterator();
while (elements.hasNext()) {
final TemplateNodeElement element = (TemplateNodeElement) elements.next();
try {
if (!elements.hasNext() && element instanceof TemplateText && (node.isEObject() || node.isList() && node.getList().size() > 0 && node.getList().get(0).isEObject())
&& ((TemplateText) element).getText().trim().length() == 0) {
break;
}
} catch (final ENodeCastException e) {
// never catch
}
final ENode child = element.evaluate(object, mode);
try {
if (element.getScript() != null && element.getScript().getFile() != null && element.getScript().getFile().getName().endsWith(".mt") //$NON-NLS-1$
&& element instanceof TemplateFeatureStatement && precedingElement instanceof TemplateText && precedingChild.isString() && precedingChild.getString().length() > 0) {
final String text = child.asString();
if (text.indexOf("\n") > -1) { //$NON-NLS-1$
final String precedingText = precedingChild.getString();
final int iLine = precedingText.lastIndexOf("\n"); //$NON-NLS-1$
if (iLine > -1 && iLine + 1 < precedingText.length()) {
final String indent = precedingText.substring(iLine + 1);
if (indent.trim().length() == 0) {
child.stringCall("internalIndent:" + indent, 0, 0); //$NON-NLS-1$
}
}
}
}
} catch (final ENodeCastException e) {
// never catch
}
node.append(child);
precedingElement = element;
precedingChild = child;
}
} finally {
if (script != null) {
script.contextPop(IScript.CURRENT_NODE);
script.contextPop(IScript.TEMPLATE_NODE);
}
if (!parentIsStatement) {
currentEval.remove(object);
}
}
}
if (postExpression != null) {
node = postExpression.evaluate(node, script, mode);
}
emptyEvaluation = node.size() == 0;
return node;
}
/**
* The current evaluations.
*/
protected List currentEval = new ArrayList();
/**
* Tells whether or not this template contains the given text, ignoring case
* or not.
*
* @param text
* is the string to be searched
* @param ignoreCase
* indicates if case differences have been eliminated
* @return true if template contains the given text
*/
public boolean containsText(String text, boolean ignoreCase) {
if (ignoreCase) {
return toString().toUpperCase().indexOf(text.toUpperCase()) > -1;
} else {
return toString().indexOf(text) > -1;
}
}
/* (non-Javadoc) */
@Override
public int hashCode() {
if (pos.b() >= 1) {
return pos.b();
} else {
return super.hashCode();
}
}
/**
* Gets the significant statements of this template : it ignores comment and
* simple text.
*
* @return the significant statements of this template.
*/
public List getSignificantStatements() {
final List result = new ArrayList();
final Iterator it = children.iterator();
while (it.hasNext()) {
final TemplateElement element = (TemplateElement) it.next();
if (element instanceof TemplateNodeElement && !(element instanceof TemplateCommentStatement) && !(element instanceof TemplateText)) {
result.add(element);
}
}
return result;
}
}