summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjmusset2009-03-05 11:49:37 (EST)
committer jmusset2009-03-05 11:49:37 (EST)
commitee6e79b6449e616027c407ba2e21a9c8218a89e4 (patch)
tree12f4a7d71c1a44153afc4568d084612a95448d67
parente89769ce63ed0432164b4249320a49bca61d1691 (diff)
downloadorg.eclipse.acceleo-ee6e79b6449e616027c407ba2e21a9c8218a89e4.zip
org.eclipse.acceleo-ee6e79b6449e616027c407ba2e21a9c8218a89e4.tar.gz
org.eclipse.acceleo-ee6e79b6449e616027c407ba2e21a9c8218a89e4.tar.bz2
Project renaming from MTL to Acceleo
-rw-r--r--plugins/org.eclipse.acceleo.engine/.checkstyle9
-rw-r--r--plugins/org.eclipse.acceleo.engine/.classpath7
-rw-r--r--plugins/org.eclipse.acceleo.engine/.project34
-rw-r--r--plugins/org.eclipse.acceleo.engine/.settings/org.eclipse.jdt.core.prefs7
-rw-r--r--plugins/org.eclipse.acceleo.engine/META-INF/MANIFEST.MF21
-rw-r--r--plugins/org.eclipse.acceleo.engine/about.html57
-rw-r--r--plugins/org.eclipse.acceleo.engine/build.properties19
-rw-r--r--plugins/org.eclipse.acceleo.engine/plugin.properties12
-rw-r--r--plugins/org.eclipse.acceleo.engine/plugin.xml17
-rw-r--r--plugins/org.eclipse.acceleo.engine/schema/dynamic.templates.exsd111
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEngineMessages.java81
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEnginePlugin.java273
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEvaluationException.java43
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/acceleoenginemessages.properties59
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationEvent.java74
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationListener.java36
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/AcceleoGenericEngine.java152
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/IAcceleoEngine.java75
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/ASTFragment.java217
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/IDebugAST.java58
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironment.java105
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironmentFactory.java148
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEvaluationEnvironment.java950
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationContext.java615
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationVisitor.java948
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/utils/AcceleoDynamicTemplatesEclipseUtil.java167
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoDynamicTemplatesRegistry.java172
-rw-r--r--plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoService.java489
28 files changed, 4956 insertions, 0 deletions
diff --git a/plugins/org.eclipse.acceleo.engine/.checkstyle b/plugins/org.eclipse.acceleo.engine/.checkstyle
new file mode 100644
index 0000000..60d2e33
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/.checkstyle
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fileset-config file-format-version="1.2.0" simple-config="true">
+ <local-check-config name="Acceleo" location="/org.eclipse.acceleo-feature/codestyle/AcceleoCheckstyleConfiguration.xml" type="project" description="">
+ <additional-data name="protect-config-file" value="false"/>
+ </local-check-config>
+ <fileset name="tous" enabled="true" check-config-name="Acceleo" local="true">
+ <file-match-pattern match-pattern="." include-pattern="true"/>
+ </fileset>
+</fileset-config>
diff --git a/plugins/org.eclipse.acceleo.engine/.classpath b/plugins/org.eclipse.acceleo.engine/.classpath
new file mode 100644
index 0000000..64c5e31
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.eclipse.acceleo.engine/.project b/plugins/org.eclipse.acceleo.engine/.project
new file mode 100644
index 0000000..e5a2890
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.acceleo.engine</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.atlassw.tools.eclipse.checkstyle.CheckstyleBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>com.atlassw.tools.eclipse.checkstyle.CheckstyleNature</nature>
+ </natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.acceleo.engine/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.acceleo.engine/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..c6c2330
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Fri May 23 13:22:43 CEST 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/plugins/org.eclipse.acceleo.engine/META-INF/MANIFEST.MF b/plugins/org.eclipse.acceleo.engine/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..be2a055
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/META-INF/MANIFEST.MF
@@ -0,0 +1,21 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.acceleo.engine;singleton:=true
+Bundle-Version: 0.8.0.qualifier
+Bundle-Vendor: %providerName
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Require-Bundle: org.eclipse.acceleo.common,
+ org.eclipse.acceleo.model,
+ org.eclipse.ocl.ecore,
+ org.eclipse.core.runtime;resolution:=optional
+Export-Package: org.eclipse.acceleo.engine,
+ org.eclipse.acceleo.engine.event,
+ org.eclipse.acceleo.engine.generation,
+ org.eclipse.acceleo.engine.internal.debug;x-friends:="org.eclipse.acceleo.ide.ui",
+ org.eclipse.acceleo.engine.internal.environment;x-friends:="org.eclipse.acceleo.engine.tests",
+ org.eclipse.acceleo.engine.internal.evaluation;x-friends:="org.eclipse.acceleo.ide.ui,org.eclipse.acceleo.engine.tests",
+ org.eclipse.acceleo.engine.service
+Bundle-Activator: org.eclipse.acceleo.engine.AcceleoEnginePlugin
+Eclipse-LazyStart: true
+Bundle-ActivationPolicy: lazy
diff --git a/plugins/org.eclipse.acceleo.engine/about.html b/plugins/org.eclipse.acceleo.engine/about.html
new file mode 100644
index 0000000..7e1117e
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/about.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+
+
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<title>About</title>
+</head><body lang="EN-US">
+<h2>About This Content</h2>
+
+<em>July 25, 2008</em></p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, "Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+
+ <h3>Third Party Content</h3>
+ <p>The Content includes items that have been sourced from third parties as set out below. If you
+ did not receive this Content directly from the Eclipse Foundation, the following is provided
+ for informational purposes only, and you should look to the Redistributor's license for
+ terms and conditions of use.</p>
+ <p><em>
+ <br><br>
+ <strong>OMG MOF 2.0 Specification (06-01-01)</strong> <br>
+ <ul>
+ <li><a href="http://www.omg.org/technology/documents/formal/mof.htm">MOF 2.0 main page</a></li>
+ <li><a href="http://www.omg.org/docs/formal/06-01-01.pdf">MOF 2.0 specification</a></li>
+ <li><a href="http://www.omg.org/">OMG site</a></li>
+ </ul>
+ <br>
+ <strong>OMG OCL 2.0 specification (06-05-01)</strong> <br>
+ <ul>
+ <li><a href="http://www.omg.org/technology/documents/formal/ocl.htm">OCL 2.0 main page</a></li>
+ <li><a href="http://www.omg.org/docs/formal/06-05-01.pdf">OCL 2.0 specification</a></li>
+ <li><a href="http://www.omg.org/">OMG site</a></li>
+ </ul>
+ <br>
+ <strong>OMG MTL 1.0 specification (08-01-16)</strong> <br>
+ <ul>
+ <li><a href="http://www.omg.org/spec/MOFM2T/1.0/">MTL 1.0 main page</a></li>
+ <li><a href="http://www.omg.org/spec/MOFM2T/1.0/PDF">MTL 1.0 specification</a></li>
+ <li><a href="http://www.omg.org/">OMG site</a></li>
+ </ul>
+ <br>
+ </em></p>
+ <p>A detailed list of fixes and clarifications with respect to those specifications may be found in the plug-in directly realizing them.</p>
+</body></html> \ No newline at end of file
diff --git a/plugins/org.eclipse.acceleo.engine/build.properties b/plugins/org.eclipse.acceleo.engine/build.properties
new file mode 100644
index 0000000..4ef204b
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/build.properties
@@ -0,0 +1,19 @@
+################################################################################
+# Copyright (c) 2008, 2009 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
+################################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ about.html,\
+ plugin.xml,\
+ schema/,\
+ plugin.properties
+Bundle-ClassPath: .
diff --git a/plugins/org.eclipse.acceleo.engine/plugin.properties b/plugins/org.eclipse.acceleo.engine/plugin.properties
new file mode 100644
index 0000000..6ca8981
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/plugin.properties
@@ -0,0 +1,12 @@
+#################################################################################
+# Copyright (c) 2008, 2009 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
+#################################################################################
+pluginName = Acceleo evaluation engine
+providerName = Eclipse Modeling Project \ No newline at end of file
diff --git a/plugins/org.eclipse.acceleo.engine/plugin.xml b/plugins/org.eclipse.acceleo.engine/plugin.xml
new file mode 100644
index 0000000..8308702
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/plugin.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+
+<!--
+Copyright (c) 2008, 2009 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
+-->
+
+<plugin>
+ <extension-point id="org.eclipse.acceleo.engine.dynamic.templates" name="org.eclipse.engine.ui.dynamic.templates" schema="schema/dynamic.templates.exsd"/>
+</plugin>
diff --git a/plugins/org.eclipse.acceleo.engine/schema/dynamic.templates.exsd b/plugins/org.eclipse.acceleo.engine/schema/dynamic.templates.exsd
new file mode 100644
index 0000000..3ace50a
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/schema/dynamic.templates.exsd
@@ -0,0 +1,111 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.acceleo.engine" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appinfo>
+ <meta.schema plugin="org.eclipse.acceleo.engine" id="org.eclipse.acceleo.engine.dynamic.templates" name="org.eclipse.acceleo.engine.dynamic.templates"/>
+ </appinfo>
+ <documentation>
+ This extension point allows a third-party plugin to indicate it defines dynamic overrides for a generation.
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <annotation>
+ <appinfo>
+ <meta.element />
+ </appinfo>
+ </annotation>
+ <complexType>
+ <sequence minOccurs="1" maxOccurs="unbounded">
+ <element ref="templates"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appinfo>
+ <meta.attribute translatable="true"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="templates">
+ <complexType>
+ <attribute name="path" type="string" use="required">
+ <annotation>
+ <documentation>
+ Path to the directory containing the templates.
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="resource"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="since"/>
+ </appinfo>
+ <documentation>
+ 0.8
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="examples"/>
+ </appinfo>
+ <documentation>
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="apiinfo"/>
+ </appinfo>
+ <documentation>
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="implementation"/>
+ </appinfo>
+ <documentation>
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="copyright"/>
+ </appinfo>
+ <documentation>
+ Copyright (c) 2009 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
+ </documentation>
+ </annotation>
+
+</schema>
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEngineMessages.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEngineMessages.java
new file mode 100644
index 0000000..9db9ff5
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEngineMessages.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007, 2008, 2009 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.acceleo.engine;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Utility class to access externalized Strings throughout the Acceleo engine.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public final class AcceleoEngineMessages {
+ /** Qualified path to the properties file in which to seek the keys. */
+ private static final String BUNDLE_NAME = "org.eclipse.acceleo.engine.acceleoenginemessages"; //$NON-NLS-1$
+
+ /** Contains the locale specific {@link String}s needed by this plug-in. */
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
+
+ /**
+ * Utility classes don't need to (and shouldn't) be instantiated.
+ */
+ private AcceleoEngineMessages() {
+ // prevents instantiation
+ }
+
+ /**
+ * This will return an unformatted String from the resource bundle.
+ *
+ * @param key
+ * Key of the String we seek.
+ * @return An unformatted String from the bundle.
+ */
+ private static String internalGetString(String key) {
+ try {
+ return RESOURCE_BUNDLE.getString(key);
+ } catch (MissingResourceException e) {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Returns the specified {@link String} from the resource bundle.
+ *
+ * @param key
+ * Key of the String we seek.
+ * @return The String from the resource bundle associated with <code>key</code>.
+ * <code>'!' + key + '!'</code> will be returned in case we didn't find it in the bundle.
+ */
+ public static String getString(String key) {
+ // Pass through MessageFormat so that we're consistent in the handling of special chars such as the
+ // apostrophe
+ return MessageFormat.format(internalGetString(key), new Object[] {});
+ }
+
+ /**
+ * Returns a String from the resource bundle bound with the given arguments.
+ *
+ * @param key
+ * Key of the String we seek.
+ * @param arguments
+ * Arguments for the String formatting.
+ * @return formatted {@link String}.
+ * @see MessageFormat#format(String, Object[])
+ */
+ public static String getString(String key, Object... arguments) {
+ if (arguments == null) {
+ return getString(key);
+ }
+ return MessageFormat.format(internalGetString(key), arguments);
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEnginePlugin.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEnginePlugin.java
new file mode 100644
index 0000000..5102c0a
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEnginePlugin.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.acceleo.engine.internal.utils.AcceleoDynamicTemplatesEclipseUtil;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.IRegistryEventListener;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoEnginePlugin extends Plugin {
+ /** The plug-in ID. */
+ public static final String PLUGIN_ID = "org.eclipse.acceleo.engine"; //$NON-NLS-1$
+
+ /** Name of the extension point to parse for template locations. */
+ private static final String DYNAMIC_TEMPLATES_EXTENSION_POINT = "org.eclipse.acceleo.engine.dynamic.templates"; //$NON-NLS-1$
+
+ /** Name of the extension point's "templates" tag. */
+ private static final String DYNAMIC_TEMPLATES_TAG_TEMPLATES = "templates"; //$NON-NLS-1$
+
+ /** Name of the extension point's templates tag "path" atribute. */
+ private static final String DYNAMIC_TEMPLATES_ATTRIBUTE_PATH = "path"; //$NON-NLS-1$
+
+ /** This plug-in's shared instance. */
+ private static AcceleoEnginePlugin plugin;
+
+ /** The registry listener that will be used to listen to dynamic templates changes. */
+ private final DynamicTemplatesRegistryListener dynamicTemplatesListener = new DynamicTemplatesRegistryListener();
+
+ /**
+ * Default constructor for the plugin.
+ */
+ public AcceleoEnginePlugin() {
+ plugin = this;
+ }
+
+ /**
+ * Returns the shared instance.
+ *
+ * @return the shared instance
+ */
+ public static AcceleoEnginePlugin getDefault() {
+ return plugin;
+ }
+
+ /**
+ * Trace an Exception in the error log.
+ *
+ * @param e
+ * Exception to log.
+ * @param blocker
+ * <code>True</code> if the exception must be logged as error, <code>False</code> to log it as
+ * a warning.
+ */
+ public static void log(Exception e, boolean blocker) {
+ if (e == null) {
+ throw new NullPointerException(AcceleoEngineMessages.getString("AcceleoEnginePlugin.LogNullException")); //$NON-NLS-1$
+ }
+
+ if (getDefault() == null) {
+ // We are out of eclipse. Prints the stack trace on standard error.
+ // CHECKSTYLE:OFF
+ e.printStackTrace();
+ // CHECKSTYLE:ON
+ } else if (e instanceof CoreException) {
+ log(((CoreException)e).getStatus());
+ } else if (e instanceof NullPointerException) {
+ int severity = IStatus.WARNING;
+ if (blocker) {
+ severity = IStatus.ERROR;
+ }
+ log(new Status(severity, PLUGIN_ID, severity, AcceleoEngineMessages
+ .getString("AcceleoEnginePlugin.ElementNotFound"), e)); //$NON-NLS-1$
+ } else {
+ int severity = IStatus.WARNING;
+ if (blocker) {
+ severity = IStatus.ERROR;
+ }
+ log(new Status(severity, PLUGIN_ID, severity, AcceleoEngineMessages
+ .getString("AcceleoEnginePlugin.JavaException"), e)); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Puts the given status in the error log view.
+ *
+ * @param status
+ * Error Status.
+ */
+ public static void log(IStatus status) {
+ // Eclipse platform displays NullPointer on standard error instead of throwing it.
+ // We'll handle this by throwing it ourselves.
+ if (status == null) {
+ throw new NullPointerException(AcceleoEngineMessages.getString("AcceleoEnginePlugin.LogNullStatus")); //$NON-NLS-1$
+ }
+
+ if (getDefault() != null) {
+ getDefault().getLog().log(status);
+ } else {
+ // CHECKSTYLE:OFF
+ System.err.println(status.getMessage());
+ status.getException().printStackTrace();
+ // CHECKSTYLE:ON
+ }
+ }
+
+ /**
+ * Puts the given message in the error log view, as error or warning.
+ *
+ * @param message
+ * The message to put in the error log view.
+ * @param blocker
+ * <code>True</code> if the message must be logged as error, <code>False</code> to log it as a
+ * warning.
+ */
+ public static void log(String message, boolean blocker) {
+ if (getDefault() == null) {
+ // We are out of eclipse. Prints the message on standard error.
+ // CHECKSTYLE:OFF
+ System.err.println(message);
+ // CHECKSTYLE:ON
+ } else {
+ int severity = IStatus.WARNING;
+ if (blocker) {
+ severity = IStatus.ERROR;
+ }
+ String errorMessage = message;
+ if (errorMessage == null || "".equals(errorMessage)) { //$NON-NLS-1$
+ errorMessage = AcceleoEngineMessages.getString("AcceleoEnginePlugin.UnexpectedException"); //$NON-NLS-1$
+ }
+ log(new Status(severity, PLUGIN_ID, errorMessage));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.runtime.Plugin#start(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void start(final BundleContext context) throws Exception {
+ super.start(context);
+ final IExtensionRegistry registry = Platform.getExtensionRegistry();
+ registry.addListener(dynamicTemplatesListener, DYNAMIC_TEMPLATES_EXTENSION_POINT);
+ parseInitialContributions();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void stop(final BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ final IExtensionRegistry registry = Platform.getExtensionRegistry();
+ registry.removeListener(dynamicTemplatesListener);
+ AcceleoDynamicTemplatesEclipseUtil.clearRegistry();
+ }
+
+ /**
+ * Though we have listeners on both provided extension points, there could have been contributions before
+ * this plugin got started. This will parse them.
+ */
+ private void parseInitialContributions() {
+ final IExtensionRegistry registry = Platform.getExtensionRegistry();
+
+ // Dynamic templates
+ for (IExtension extension : registry.getExtensionPoint(DYNAMIC_TEMPLATES_EXTENSION_POINT)
+ .getExtensions()) {
+ final IConfigurationElement[] configElements = extension.getConfigurationElements();
+ final List<String> pathes = new ArrayList<String>(configElements.length);
+ for (IConfigurationElement elem : configElements) {
+ if (DYNAMIC_TEMPLATES_TAG_TEMPLATES.equals(elem.getName())) {
+ pathes.add(elem.getAttribute(DYNAMIC_TEMPLATES_ATTRIBUTE_PATH));
+ }
+ }
+ final Bundle bundle = Platform.getBundle(extension.getContributor().getName());
+ // If bundle is null, the bundle id is different than its name.
+ if (bundle != null) {
+ AcceleoDynamicTemplatesEclipseUtil.addExtendingBundle(bundle, pathes);
+ }
+ }
+ }
+
+ /**
+ * This listener will allow us to be aware of contribution changes against the dynamic templates extension
+ * point.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+ final class DynamicTemplatesRegistryListener implements IRegistryEventListener {
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.runtime.IRegistryEventListener#added(org.eclipse.core.runtime.IExtension[])
+ */
+ public void added(IExtension[] extensions) {
+ for (IExtension extension : extensions) {
+ final IConfigurationElement[] configElements = extension.getConfigurationElements();
+ final List<String> pathes = new ArrayList<String>(configElements.length);
+ for (IConfigurationElement elem : configElements) {
+ if (DYNAMIC_TEMPLATES_TAG_TEMPLATES.equals(elem.getName())) {
+ pathes.add(elem.getAttribute(DYNAMIC_TEMPLATES_ATTRIBUTE_PATH));
+ }
+ }
+ final Bundle bundle = Platform.getBundle(extension.getContributor().getName());
+ // If bundle is null, the bundle id is different than its name.
+ if (bundle != null) {
+ AcceleoDynamicTemplatesEclipseUtil.addExtendingBundle(bundle, pathes);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.runtime.IRegistryEventListener#added(org.eclipse.core.runtime.IExtensionPoint[])
+ */
+ public void added(IExtensionPoint[] extensionPoints) {
+ // no need to listen to this event
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.runtime.IRegistryEventListener#removed(org.eclipse.core.runtime.IExtension[])
+ */
+ public void removed(IExtension[] extensions) {
+ for (IExtension extension : extensions) {
+ final Bundle bundle = Platform.getBundle(extension.getContributor().getName());
+ // If bundle is null, the bundle id is different than its name.
+ if (bundle != null) {
+ AcceleoDynamicTemplatesEclipseUtil.removeExtendingBundle(bundle);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.runtime.IRegistryEventListener#removed(org.eclipse.core.runtime.IExtensionPoint[])
+ */
+ public void removed(IExtensionPoint[] extensionPoints) {
+ // no need to listen to this event
+ }
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEvaluationException.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEvaluationException.java
new file mode 100644
index 0000000..0acd0f7
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/AcceleoEvaluationException.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine;
+
+/**
+ * This exception will be used to wrap exceptions occuring in the process of evaluating Acceleo modules.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoEvaluationException extends RuntimeException {
+ /** Serial version UID. Used for deserialization. */
+ private static final long serialVersionUID = 7032034280090167507L;
+
+ /**
+ * Instantiates an evaluation exception given its error message.
+ *
+ * @param message
+ * The exception details' message.
+ */
+ public AcceleoEvaluationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Instantiates an evaluation exception wrapped around its cause.
+ *
+ * @param message
+ * The exception details' message.
+ * @param cause
+ * Cause of this exception.
+ */
+ public AcceleoEvaluationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/acceleoenginemessages.properties b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/acceleoenginemessages.properties
new file mode 100644
index 0000000..73eff68
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/acceleoenginemessages.properties
@@ -0,0 +1,59 @@
+################################################################################
+# Copyright (c) 2008, 2009 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
+################################################################################
+## note : apostrophes need to be doubled in these messages or they'll be ignored
+# used throughout the code
+usercode.start = Start of user code
+usercode.end = End of user code
+
+#org.eclipse.acceleo.engine
+AcceleoEnginePlugin.ElementNotFound = Required element not found.
+AcceleoEnginePlugin.JavaException = A java exception has been thrown.
+AcceleoEnginePlugin.LogNullStatus = Status to be logged cannot be null.
+AcceleoEnginePlugin.LogNullException = Exception to be logged cannot be null.
+AcceleoEnginePlugin.UnexpectedException = Unexpected Acceleo problem.
+
+# org.eclipse.acceleo.engine.environment
+AcceleoEnvironmentFactory.IllegalParent = Parent environment must be an Acceleo environment: {0}.
+
+AcceleoEvaluationEnvironment.IllegalTokenizerFlag = Invalid flag : {0}. Can be either 0 or 1.
+AcceleoEvaluationEnvironment.ModuleResolutionError = Error while resolving module dependencies.
+AcceleoEvaluationEnvironment.UndefinedOperation = Operation {0}({1}) is undefined on type {2}.
+
+# org.eclipse.acceleo.engine.evaluation
+AcceleoEvaluationContext.AppendError = Could not append text to the buffer.
+AcceleoEvaluationContext.CleanUpError = Could not dispose open buffers.
+AcceleoEvaluationContext.FolderCreationError = Could not create directory {0}.
+AcceleoEvaluationContext.FileCreationError = Could not create file {0}.
+AcceleoEvaluationContext.FlushError = Could not flush the previous buffer.
+AcceleoEvaluationContext.LostContent = Could not create lost file for {0}. Lost content : {1}
+AcceleoEvaluationContext.WriteError = Could not write to the buffer''s target.
+
+AcceleoEvaluationVisitor.NullForIteration = Empty loop iteration at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedFileURL = Couldn't evaluate URL of the file at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedGuard = Undefined guard on Acceleo block at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedCondition = Undefined condition of "If" at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedElseCondition = Undefined condition of "Else" at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedLetValue = Undefined "Let" expression at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedElseLetValue = Undefined "Else Let" expression at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedAreaMarker = Undefined protected area marker at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedArgument = Undefined argument of invocation at position {0} in Module {1}.
+AcceleoEvaluationVisitor.UndefinedVariable = Undefined variable for init section at position {0} in Module {1}.
+
+# org.eclipse.acceleo.engine.generation
+AcceleoEngine.ArgumentMismatch = Argument types mismatch for template {0}.
+AcceleoEngine.IllegalArguments = All arguments of a template must be set.
+AcceleoEngine.IllegalTemplateInvocation = Cannot invoke non-public template for generation.
+AcceleoEngine.NullArguments = Arguments of a generation cannot be null.
+AcceleoEngine.VoidArguments = Multi-arguments templates must be called with set parameters.
+
+# org.eclipse.acceleo.engine.service
+AcceleoService.UndefinedTemplate = Could not find public template {0} in module {1}.
+AcceleoService.NullArguments = Arguments of a generation cannot be null. \ No newline at end of file
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationEvent.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationEvent.java
new file mode 100644
index 0000000..135d021
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationEvent.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.event;
+
+import org.eclipse.acceleo.model.mtl.Block;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * This event will be fired when text is generated. It will hold references to the generated text, the object
+ * which triggered the generation and the Block from which it was generated.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoTextGenerationEvent {
+ /** This will hold the generated text. */
+ private final String text;
+
+ /** This field will hold a reference towards the originating block. */
+ private final Block block;
+
+ /** The object which triggered generation of this text. */
+ private final EObject source;
+
+ /**
+ * Instantiates a text generation event.
+ *
+ * @param generatedText
+ * Text which generation triggered this event.
+ * @param block
+ * The block from which <code>generatedText</code> has been generated.
+ * @param source
+ * EObject which triggered the generation.
+ */
+ public AcceleoTextGenerationEvent(String generatedText, Block block, EObject source) {
+ this.text = generatedText;
+ this.block = block;
+ this.source = source;
+ }
+
+ /**
+ * Returns the generated text.
+ *
+ * @return The generated text.
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * returns the originating block.
+ *
+ * @return The originating block.
+ */
+ public Block getBlock() {
+ return block;
+ }
+
+ /**
+ * Returns the object which triggered generation of this text.
+ *
+ * @return The object which triggered generation of this text.
+ */
+ public EObject getSource() {
+ return source;
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationListener.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationListener.java
new file mode 100644
index 0000000..742a9e0
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/event/AcceleoTextGenerationListener.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.event;
+
+/**
+ * Instances of this listener will be notified whenever text is generated from an Acceleo Block.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public interface AcceleoTextGenerationListener {
+ /**
+ * This will be called whenever text is generated from an Acceleo Block.
+ *
+ * @param event
+ * This will hold information on the generation details.
+ */
+ void textGenerated(AcceleoTextGenerationEvent event);
+
+ /**
+ * This will be called by the engine whenever a path has been calculated for a file block. In essence, it
+ * tells registered listener that a given source EObject will generate a file at the given path. Path can
+ * be retrieved as the text from the event.
+ *
+ * @param event
+ * This will hold information on the generation details.
+ */
+ void filePathComputed(AcceleoTextGenerationEvent event);
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/AcceleoGenericEngine.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/AcceleoGenericEngine.java
new file mode 100644
index 0000000..bc1fd8e
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/AcceleoGenericEngine.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.generation;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.acceleo.engine.AcceleoEngineMessages;
+import org.eclipse.acceleo.engine.AcceleoEvaluationException;
+import org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener;
+import org.eclipse.acceleo.engine.internal.environment.AcceleoEnvironment;
+import org.eclipse.acceleo.engine.internal.environment.AcceleoEnvironmentFactory;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.acceleo.model.mtl.Template;
+import org.eclipse.acceleo.model.mtl.VisibilityKind;
+import org.eclipse.ocl.ecore.OCL;
+import org.eclipse.ocl.ecore.OCL.Query;
+import org.eclipse.ocl.ecore.Variable;
+
+
+/**
+ * This class can be used to launch the generation of an Acceleo template.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoGenericEngine implements IAcceleoEngine {
+ /**
+ * This will hold the list of all listeners registered for notification on text generation from this
+ * engine.
+ */
+ private final List<AcceleoTextGenerationListener> listeners = new ArrayList<AcceleoTextGenerationListener>(3);
+
+ /** Holds a reference to the ocl instance. */
+ private OCL ocl;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.acceleo.engine.generation.IAcceleoEngine#addListener(org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener)
+ */
+ public void addListener(AcceleoTextGenerationListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.acceleo.engine.generation.IAcceleoEngine#evaluate(org.eclipse.acceleo.model.mtl.Template, java.util.List,
+ * java.io.File, boolean)
+ */
+ public Map<String, StringWriter> evaluate(Template template, List<? extends Object> arguments,
+ File generationRoot, boolean preview) {
+ if (template == null || arguments == null || (!preview && generationRoot == null)) {
+ throw new NullPointerException(AcceleoEngineMessages.getString("AcceleoEngine.NullArguments")); //$NON-NLS-1$
+ }
+ if (template.getVisibility() != VisibilityKind.PUBLIC) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEngine.IllegalTemplateInvocation")); //$NON-NLS-1$
+ }
+ if (template.getParameter().size() != arguments.size()) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoEngine.IllegalArguments")); //$NON-NLS-1$
+ }
+
+ // We need to create an OCL instance for each generation since the environment factory is contextual
+ AcceleoEnvironmentFactory factory = new AcceleoEnvironmentFactory(generationRoot, (Module)template
+ .eContainer(), new ArrayList<AcceleoTextGenerationListener>(listeners), preview);
+ ocl = OCL.newInstance(factory);
+ ((AcceleoEnvironment)ocl.getEnvironment()).restoreBrokenEnvironmentPackages(template.eResource());
+
+ doEvaluate(template, arguments);
+
+ if (preview) {
+ return factory.getEvaluationPreview();
+ }
+ return Collections.<String, StringWriter> emptyMap();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.acceleo.engine.generation.IAcceleoEngine#removeListener(org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener)
+ */
+ public void removeListener(AcceleoTextGenerationListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * This does the actual work of template evaluation. It will be called from the public API methods exposed
+ * by the engine.
+ *
+ * @param template
+ * The template which is to be evaluated.
+ * @param arguments
+ * These will be passed as the template arguments.
+ */
+ private void doEvaluate(Template template, List<? extends Object> arguments) {
+ // Guard Evaluation
+ boolean guardValue = true;
+ if (template.getGuard() != null) {
+ final Query guard = ocl.createQuery(template.getGuard());
+ // Sets all needed variables for the guard evaluation
+ for (int i = 0; i < template.getParameter().size(); i++) {
+ Variable param = template.getParameter().get(i);
+ Object value = arguments.get(i);
+ if (param.getType().isInstance(value)) {
+ guard.getEvaluationEnvironment().add(param.getName(), value);
+ } else {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ "AcceleoEngine.ArgumentMismatch", template.getName())); //$NON-NLS-1$
+ }
+ // [255379] also sets "self" variable to match the very first parameter
+ if (i == 0) {
+ guard.getEvaluationEnvironment().add("self", value); //$NON-NLS-1$
+ }
+ }
+ guardValue = ((Boolean)guard.evaluate()).booleanValue();
+ }
+
+ // If there were no guard or its condition is verified, evaluate the template now.
+ if (guardValue) {
+ final Query query = ocl.createQuery(template);
+ // Sets all needed variables for the template evaluation
+ for (int i = 0; i < template.getParameter().size(); i++) {
+ Variable param = template.getParameter().get(i);
+ Object value = arguments.get(i);
+ if (param.getType().isInstance(value)) {
+ query.getEvaluationEnvironment().add(param.getName(), value);
+ } else {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ "AcceleoEngine.ArgumentMismatch", template.getName())); //$NON-NLS-1$
+ }
+ // [255379] also sets "self" variable to match the very first parameter
+ if (i == 0) {
+ query.getEvaluationEnvironment().add("self", value); //$NON-NLS-1$
+ }
+ }
+ query.evaluate();
+ }
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/IAcceleoEngine.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/IAcceleoEngine.java
new file mode 100644
index 0000000..9d2d4ca
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/generation/IAcceleoEngine.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.generation;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener;
+import org.eclipse.acceleo.model.mtl.Template;
+
+/**
+ * Base interface for all implementation of an Acceleo evaluation engine.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public interface IAcceleoEngine {
+ /**
+ * Registers a listener to be notified for any text generation that will take place in this engine
+ * evaluation process.
+ *
+ * @param listener
+ * The new listener that is to be registered for notification.
+ */
+ void addListener(AcceleoTextGenerationListener listener);
+
+ /**
+ * Evaluates the given Acceleo Template with the given arguments.
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for the argument.
+ * </p>
+ *
+ * @param template
+ * The Acceleo template which is to be evaluated.
+ * @param arguments
+ * List of the template's arguments.
+ * @param generationRoot
+ * This will be used as the root for the generated files.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ Map<String, StringWriter> evaluate(Template template, List<? extends Object> arguments,
+ File generationRoot, boolean preview);
+
+ /**
+ * Removes a listener from the notification loops.
+ *
+ * @param listener
+ * The listener that is to be removed from this engine's notification loops.
+ */
+ void removeListener(AcceleoTextGenerationListener listener);
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/ASTFragment.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/ASTFragment.java
new file mode 100644
index 0000000..deb9e43
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/ASTFragment.java
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.internal.debug;
+
+import org.eclipse.core.runtime.Path;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+
+/**
+ * The fragment URI of an AST node.
+ *
+ * @author <a href="mailto:jonathan.musset@obeo.fr">Jonathan Musset</a>
+ */
+public class ASTFragment {
+
+ /**
+ * Inner separator between the file short name and the EObject fragment URI.
+ */
+ private static final String SEPARATOR = "|"; //$NON-NLS-1$
+
+ /**
+ * The generic pattern used to specify that every object name is valid.
+ */
+ private static final String ALL_NAMES_FILTER = "*"; //$NON-NLS-1$
+
+ /**
+ * Short name of the file, it means the name of the file without extension.
+ */
+ private String fileShortName;
+
+ /**
+ * The EObject fragment URI.
+ */
+ private String fragmentURI;
+
+ /**
+ * Indicates if the AST fragment is empty.
+ */
+ private boolean empty;
+
+ /**
+ * The EObject name filter. It is used to filter the objects which suspend the debug thread.
+ */
+ private String eObjectNameFilter;
+
+ /**
+ * Constructor.
+ *
+ * @param astNode
+ * is the AST node
+ */
+ public ASTFragment(EObject astNode) {
+ super();
+ if (astNode == null) {
+ this.fileShortName = ""; //$NON-NLS-1$
+ this.fragmentURI = ""; //$NON-NLS-1$
+ this.empty = true;
+ } else if (astNode.eResource() != null) {
+ this.fileShortName = new Path(astNode.eResource().getURI().lastSegment()).removeFileExtension()
+ .lastSegment();
+ this.fragmentURI = astNode.eResource().getURIFragment(astNode);
+ this.empty = false;
+ } else {
+ this.fileShortName = ""; //$NON-NLS-1$
+ this.fragmentURI = EcoreUtil.getURI(astNode).toString();
+ this.empty = this.fragmentURI.length() == 0;
+ }
+ this.eObjectNameFilter = ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Constructor.
+ * <p>
+ * a = new ASTFragment(eObject);
+ * </p>
+ * <p>
+ * b = new ASTFragment(a.toString());
+ * </p>
+ * <p>
+ * a.equals(b) is true
+ * </p>
+ *
+ * @param string
+ * is the string representation of an old AST node
+ */
+ public ASTFragment(String string) {
+ super();
+ if (string == null) {
+ this.fileShortName = ""; //$NON-NLS-1$
+ this.fragmentURI = ""; //$NON-NLS-1$
+ this.empty = true;
+ this.eObjectNameFilter = ""; //$NON-NLS-1$
+ } else {
+ int i = string.indexOf(SEPARATOR);
+ if (i > -1) {
+ this.fileShortName = string.substring(0, i);
+ int j = string.indexOf(SEPARATOR, i + SEPARATOR.length());
+ if (j > -1) {
+ this.eObjectNameFilter = string.substring(j + SEPARATOR.length());
+ } else {
+ j = string.length();
+ this.eObjectNameFilter = ""; //$NON-NLS-1$
+ }
+ this.fragmentURI = string.substring(i + SEPARATOR.length(), j);
+ this.empty = this.fileShortName.length() == 0 && this.fragmentURI.length() == 0;
+ } else {
+ this.fileShortName = ""; //$NON-NLS-1$
+ this.fragmentURI = string;
+ this.empty = this.fragmentURI.length() == 0;
+ this.eObjectNameFilter = ""; //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Sets the EObject name filter.
+ *
+ * @return the EObject name filter
+ */
+ public String getEObjectNameFilter() {
+ return eObjectNameFilter;
+ }
+
+ /**
+ * Gets the EObject name filter.
+ *
+ * @param nameFilter
+ * the EObject name filter
+ */
+ public void setEObjectNameFilter(String nameFilter) {
+ eObjectNameFilter = nameFilter;
+ }
+
+ /**
+ * Indicates if the AST fragment is empty.
+ *
+ * @return true if the AST fragment is empty
+ */
+ public boolean isEmpty() {
+ return empty;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return (fileShortName + fragmentURI).hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ASTFragment) {
+ ASTFragment other = (ASTFragment)obj;
+ boolean result;
+ if (fileShortName.equals(other.fileShortName) && fragmentURI.equals(other.fragmentURI)) {
+ if ("".equals(eObjectNameFilter) || "".equals(other.eObjectNameFilter) //$NON-NLS-1$ //$NON-NLS-2$
+ || ALL_NAMES_FILTER.equals(eObjectNameFilter)
+ || ALL_NAMES_FILTER.equals(other.eObjectNameFilter)) {
+ result = true;
+ } else if (eObjectNameFilter.startsWith(ALL_NAMES_FILTER)
+ && other.eObjectNameFilter.endsWith(eObjectNameFilter.substring(ALL_NAMES_FILTER
+ .length()))) {
+ result = true;
+ } else if (other.eObjectNameFilter.startsWith(ALL_NAMES_FILTER)
+ && eObjectNameFilter.endsWith(other.eObjectNameFilter.substring(ALL_NAMES_FILTER
+ .length()))) {
+ result = true;
+ } else if (eObjectNameFilter.endsWith(ALL_NAMES_FILTER)
+ && other.eObjectNameFilter.startsWith(eObjectNameFilter.substring(0,
+ eObjectNameFilter.length() - ALL_NAMES_FILTER.length()))) {
+ result = true;
+ } else if (other.eObjectNameFilter.endsWith(ALL_NAMES_FILTER)
+ && eObjectNameFilter.startsWith(other.eObjectNameFilter.substring(0,
+ other.eObjectNameFilter.length() - ALL_NAMES_FILTER.length()))) {
+ result = true;
+ } else {
+ result = eObjectNameFilter.equals(other.eObjectNameFilter);
+ }
+ } else {
+ result = false;
+ }
+ return result;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ if (empty) {
+ return ""; //$NON-NLS-1$
+ } else {
+ return fileShortName + SEPARATOR + fragmentURI + SEPARATOR + eObjectNameFilter;
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/IDebugAST.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/IDebugAST.java
new file mode 100644
index 0000000..4d20376
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/debug/IDebugAST.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.internal.debug;
+
+/**
+ * The interface to implement to be able to debug an AST evaluation.
+ *
+ * @author <a href="mailto:jonathan.musset@obeo.fr">Jonathan Musset</a>
+ */
+public interface IDebugAST {
+
+ /**
+ * Start debugging an AST node.
+ *
+ * @param astFragment
+ * is the fragment of the AST node to debug
+ */
+ void startDebug(ASTFragment astFragment);
+
+ /**
+ * Update the input of the debugger for the current AST node.
+ *
+ * @param astFragment
+ * is the fragment of the AST node to debug
+ * @param input
+ * is the new input
+ */
+ void stepDebugInput(ASTFragment astFragment, Object input);
+
+ /**
+ * Update the output of the debugger for the current AST node.
+ *
+ * @param astFragment
+ * is the fragment of the AST node to debug
+ * @param input
+ * is the current input
+ * @param output
+ * is the new output
+ */
+ void stepDebugOutput(ASTFragment astFragment, Object input, Object output);
+
+ /**
+ * Stop debugging an AST node.
+ *
+ * @param astFragment
+ * is the fragment of the AST node to debug
+ */
+ void endDebug(ASTFragment astFragment);
+
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironment.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironment.java
new file mode 100644
index 0000000..8c03bff
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironment.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.internal.environment;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EEnumLiteral;
+import org.eclipse.emf.ecore.EFactory;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EOperation;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EParameter;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.ocl.AbstractTypeResolver;
+import org.eclipse.ocl.EnvironmentFactory;
+import org.eclipse.ocl.ecore.CallOperationAction;
+import org.eclipse.ocl.ecore.Constraint;
+import org.eclipse.ocl.ecore.EcoreEnvironment;
+import org.eclipse.ocl.ecore.SendSignalAction;
+
+/**
+ * The environment that will be used throughout the evaluation of Acceleo templates.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoEnvironment extends EcoreEnvironment {
+ /**
+ * This map will be populated with the factories towards which we'll need to restore broken links. See
+ * {@link #restoreBrokenEnvironmentPackages(Resource)}.
+ */
+ private final Map<String, EFactory> factories = new HashMap<String, EFactory>(4);
+
+ /**
+ * Delegates instantiation to the super-constructor.
+ *
+ * @param reg
+ * The package registry.
+ */
+ protected AcceleoEnvironment(EPackage.Registry reg) {
+ super(reg);
+ }
+
+ /**
+ * Restores the broken packages' links toward the environment.
+ * <p>
+ * When the parsed template is saved as a model, {@link EFactory factories} instance aren't persisted with
+ * it. This step is needed in order for the evaluation to be able to find these factories back.
+ * <p>
+ *
+ * @param resource
+ * Resource of the emtl file on which packages are to be fixed.
+ */
+ public void restoreBrokenEnvironmentPackages(Resource resource) {
+ if (factories.isEmpty()) {
+ // This needs to be kept in sync with new developments
+ @SuppressWarnings("unchecked")
+ final AbstractTypeResolver<EPackage, ?, ?, ?, ?> typeResolver = (AbstractTypeResolver<EPackage, ?, ?, ?, ?>)getTypeResolver();
+ final EPackage tuplePackage = typeResolver.getTuplePackage();
+ final EPackage typePackage = typeResolver.getTypePackage();
+ final EPackage collectionPackage = typeResolver.getCollectionPackage();
+ final EPackage additionsPackage = typeResolver.getAdditionalFeaturesPackage();
+
+ factories.put(tuplePackage.getName(), tuplePackage.getEFactoryInstance());
+ factories.put(typePackage.getName(), typePackage.getEFactoryInstance());
+ factories.put(collectionPackage.getName(), collectionPackage.getEFactoryInstance());
+ factories.put(additionsPackage.getName(), additionsPackage.getEFactoryInstance());
+ }
+
+ for (EObject element : resource.getContents()) {
+ if (element instanceof EPackage) {
+ final String packageName = ((EPackage)element).getName();
+ final EFactory factory = factories.get(packageName);
+ if (factory != null) {
+ ((EPackage)element).setEFactoryInstance(factory);
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-javadoc) This has been overriden here to avoid encapsulation ...
+ */
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.ecore.EcoreEnvironment#setFactory(org.eclipse.ocl.EnvironmentFactory)
+ */
+ @Override
+ protected void setFactory(
+ EnvironmentFactory<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> factory) {
+ super.setFactory(factory);
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironmentFactory.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironmentFactory.java
new file mode 100644
index 0000000..10e3fc3
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEnvironmentFactory.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.internal.environment;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.acceleo.engine.AcceleoEngineMessages;
+import org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener;
+import org.eclipse.acceleo.engine.internal.evaluation.AcceleoEvaluationContext;
+import org.eclipse.acceleo.engine.internal.evaluation.AcceleoEvaluationVisitor;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EEnumLiteral;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EOperation;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EParameter;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.ocl.Environment;
+import org.eclipse.ocl.EvaluationEnvironment;
+import org.eclipse.ocl.EvaluationVisitor;
+import org.eclipse.ocl.ecore.CallOperationAction;
+import org.eclipse.ocl.ecore.Constraint;
+import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
+import org.eclipse.ocl.ecore.SendSignalAction;
+
+/**
+ * Represents the environment factory used to evaluate Acceleo template.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoEnvironmentFactory extends EcoreEnvironmentFactory {
+ /**
+ * Generation context for this factory. This will be shared by both the evaluation environment and
+ * visitor.
+ */
+ private final AcceleoEvaluationContext context;
+
+ /** Module for which this environment factory has been created. */
+ private final Module module;
+
+ /**
+ * Default constructor. Packages will be looked up into the global EMF registry.
+ *
+ * @param generationRoot
+ * Root of all files that will be generated.
+ * @param module
+ * The module for which this factory is to be created.
+ * @param listeners
+ * The list of all listeners that are to be notified for text generation from this context.
+ * @param preview
+ * Tells the evaluation context that it is currently in preview mode.
+ */
+ public AcceleoEnvironmentFactory(File generationRoot, Module module,
+ List<AcceleoTextGenerationListener> listeners, boolean preview) {
+ super(EPackage.Registry.INSTANCE);
+ context = new AcceleoEvaluationContext(generationRoot, listeners, preview);
+ this.module = module;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.ecore.EcoreEnvironmentFactory#createEnvironment()
+ */
+ @Override
+ public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEnvironment() {
+ AcceleoEnvironment result = new AcceleoEnvironment(getEPackageRegistry());
+ result.setFactory(this);
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.ecore.EcoreEnvironmentFactory#createEnvironment(org.eclipse.ocl.Environment)
+ */
+ @Override
+ public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEnvironment(
+ Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
+ if (!(parent instanceof AcceleoEnvironment)) {
+ throw new IllegalArgumentException(AcceleoEngineMessages.getString(
+ "AcceleoEnvironmentFactory.IllegalParent", parent.getClass().getName())); //$NON-NLS-1$
+ }
+
+ AcceleoEnvironment result = new AcceleoEnvironment(getEPackageRegistry());
+ result.setFactory(this);
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.ecore.EcoreEnvironmentFactory#createEvaluationEnvironment()
+ */
+ @Override
+ public EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> createEvaluationEnvironment() {
+ return new AcceleoEvaluationEnvironment(module);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.ecore.EcoreEnvironmentFactory#createEvaluationEnvironment(org.eclipse.ocl.EvaluationEnvironment)
+ */
+ @Override
+ public EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> createEvaluationEnvironment(
+ EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent) {
+ return new AcceleoEvaluationEnvironment(parent, module);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.AbstractEnvironmentFactory#createEvaluationVisitor(org.eclipse.ocl.Environment,
+ * org.eclipse.ocl.EvaluationEnvironment, java.util.Map)
+ */
+ @Override
+ public EvaluationVisitor<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEvaluationVisitor(
+ Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> env,
+ EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> evalEnv,
+ Map<? extends EClass, ? extends Set<? extends EObject>> extentMap) {
+ return new AcceleoEvaluationVisitor<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject>(
+ super.createEvaluationVisitor(env, evalEnv, extentMap), context);
+ }
+
+ /**
+ * Returns the preview of the generation handled by this factory's generation context.
+ *
+ * @return The preview of the generation handled by this factory's generation context.
+ */
+ public Map<String, StringWriter> getEvaluationPreview() {
+ return context.getGenerationPreview();
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEvaluationEnvironment.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEvaluationEnvironment.java
new file mode 100644
index 0000000..5e574d2
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/environment/AcceleoEvaluationEnvironment.java
@@ -0,0 +1,950 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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
+ * Jerome Benois - eInverse initial implementation
+ * Goulwen Le Fur - caching of the eInverse cross referencer
+ *******************************************************************************/
+package org.eclipse.acceleo.engine.internal.environment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.acceleo.common.utils.AcceleoNonStandardLibrary;
+import org.eclipse.acceleo.common.utils.AcceleoStandardLibrary;
+import org.eclipse.acceleo.engine.AcceleoEngineMessages;
+import org.eclipse.acceleo.engine.AcceleoEvaluationException;
+import org.eclipse.acceleo.engine.service.AcceleoDynamicTemplatesRegistry;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.acceleo.model.mtl.ModuleElement;
+import org.eclipse.acceleo.model.mtl.Template;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EDataType;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EOperation;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.EStructuralFeature.Setting;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.util.EcoreUtil.CrossReferencer;
+import org.eclipse.ocl.EvaluationEnvironment;
+import org.eclipse.ocl.ecore.EcoreEvaluationEnvironment;
+
+/**
+ * This will allow us to accurately evaluate custom operations defined in the Acceleo standard library and resolve
+ * the right template for each call (guards, overrides, namesakes, ...).
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class AcceleoEvaluationEnvironment extends EcoreEvaluationEnvironment {
+ /** Maps dynamic overrides as registered in the {@link AcceleoDynamicTemplatesRegistry}. */
+ private final Map<Template, Set<Template>> dynamicOverrides = new HashMap<Template, Set<Template>>();
+
+ /** Maps all overriding templates to their <code>super</code>. */
+ private final Map<Template, Set<Template>> overridingTemplates = new HashMap<Template, Set<Template>>();
+
+ /**
+ * Keeps track of the cross referencer that's been created for this evaluation, if any. This is used and
+ * will be instantiated by the eInverse() non standard operation.
+ */
+ private CrossReferencer referencer;
+
+ /** This will allow us to map all accessible templates to their name. */
+ private final Map<String, Set<Template>> templates = new HashMap<String, Set<Template>>();
+
+ /**
+ * Maps a source String to its StringTokenizer. Needed for the implementation of the standard operation
+ * "strtok(String, Integer)" as currently specified.
+ */
+ private final Map<String, StringTokenizer> tokenizers = new HashMap<String, StringTokenizer>();
+
+ /**
+ * This constructor is needed by the factory.
+ *
+ * @param parent
+ * Parent evaluation environment.
+ * @param module
+ * We will resolve dependencies for this module and keep references to all accessible
+ * templates.
+ */
+ public AcceleoEvaluationEnvironment(
+ EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent,
+ Module module) {
+ super(parent);
+ mapAllTemplates(module);
+ mapDynamicOverrides();
+ }
+
+ /**
+ * This constructor will create our environment given the module from which to resolve dependencies.
+ *
+ * @param module
+ * We will resolve dependencies for this module and keep references to all accessible
+ * templates.
+ */
+ public AcceleoEvaluationEnvironment(Module module) {
+ super();
+ mapAllTemplates(module);
+ mapDynamicOverrides();
+ }
+
+ /**
+ * The environment will delegate operation calls to this method if it needs to evaluate non-standard Acceleo
+ * operations.
+ *
+ * @param operation
+ * Operation which is to be evaluated.
+ * @param source
+ * Source on which the operations is evaluated.
+ * @param args
+ * Arguments of the call.
+ * @return Result of the operation call.
+ */
+ public Object callNonStandardOperation(EOperation operation, Object source, Object... args) {
+ Object result = null;
+ final String operationName = operation.getName();
+ // Specifications of each non-standard operation can be found as comments of
+ // AcceleoNonStandardLibrary#OPERATION_*.
+ if (AcceleoNonStandardLibrary.OPERATION_OCLANY_TOSTRING.equals(operationName)) {
+ result = source.toString();
+ } else if (source instanceof String) {
+ final String sourceValue = (String)source;
+
+ if (AcceleoNonStandardLibrary.OPERATION_STRING_SUBSTITUTEALL.equals(operationName)) {
+ result = substitute(sourceValue, (String)args[0], (String)args[1], true);
+ } else if (AcceleoNonStandardLibrary.OPERATION_STRING_REPLACE.equals(operationName)) {
+ result = sourceValue.replaceFirst((String)args[0], (String)args[1]);
+ } else if (AcceleoNonStandardLibrary.OPERATION_STRING_REPLACEALL.equals(operationName)) {
+ result = sourceValue.replaceAll((String)args[0], (String)args[1]);
+ } else if (AcceleoNonStandardLibrary.OPERATION_STRING_ENDSWITH.equals(operationName)) {
+ result = sourceValue.endsWith((String)args[0]);
+ } else if (AcceleoNonStandardLibrary.OPERATION_STRING_STARTSWITH.equals(operationName)) {
+ result = sourceValue.startsWith((String)args[0]);
+ } else if (AcceleoNonStandardLibrary.OPERATION_STRING_TRIM.equals(operationName)) {
+ result = sourceValue.trim();
+ } else if (AcceleoNonStandardLibrary.OPERATION_STRING_TOKENIZE.equals(operationName)) {
+ result = tokenize(sourceValue, (String)args[0]);
+ }
+ } else if (source instanceof EObject) {
+ final EObject sourceValue = (EObject)source;
+
+ if (AcceleoNonStandardLibrary.OPERATION_OCLANY_EALLCONTENTS.equals(operationName)) {
+ if (args.length == 0) {
+ result = eAllContents(sourceValue, null);
+ } else if (args.length == 1 && args[0] instanceof EClassifier) {
+ result = eAllContents(sourceValue, (EClassifier)args[0]);
+ }
+ // fall through : let else fail in UnsupportedOperationException
+ } else if (AcceleoNonStandardLibrary.OPERATION_OCLANY_ANCESTORS.equals(operationName)) {
+ result = ancestors(sourceValue);
+ } else if (AcceleoNonStandardLibrary.OPERATION_OCLANY_SIBLINGS.equals(operationName)) {
+ result = siblings(sourceValue);
+ } else if (AcceleoNonStandardLibrary.OPERATION_OCLANY_EINVERSE.equals(operationName)) {
+ result = eInverse(sourceValue);
+ }
+ }
+
+ if (result != null) {
+ return result;
+ }
+
+ // If we're here, the operation is undefined.
+ final StringBuilder argErrorMsg = new StringBuilder();
+ for (int i = 0; i < args.length; i++) {
+ argErrorMsg.append(args[i].getClass().getName());
+ if (i < args.length - 1) {
+ argErrorMsg.append(", "); //$NON-NLS-1$
+ }
+ }
+ final String sourceLabel;
+ if (source == null) {
+ sourceLabel = "null"; //$NON-NLS-1$
+ } else {
+ sourceLabel = source.getClass().getName();
+ }
+ throw new UnsupportedOperationException(AcceleoEngineMessages.getString(
+ "AcceleoEvaluationEnvironment.UndefinedOperation", operation.getName(), argErrorMsg.toString(), //$NON-NLS-1$
+ sourceLabel));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.ecore.EcoreEvaluationEnvironment#callOperation(org.eclipse.emf.ecore.EOperation,
+ * int, java.lang.Object, java.lang.Object[])
+ */
+ @Override
+ public Object callOperation(EOperation operation, int opcode, Object source, Object[] args) {
+ Object result = null;
+ if (operation.getEAnnotation("MTL") != null) { //$NON-NLS-1$
+ result = callStandardOperation(operation, source, args);
+ } else if (operation.getEAnnotation("MTL non-standard") != null) { //$NON-NLS-1$
+ result = callNonStandardOperation(operation, source, args);
+ } else {
+ result = super.callOperation(operation, opcode, source, args);
+ }
+ return result;
+ }
+
+ /**
+ * The environment will delegate operation calls to this method if it needs to evaluate a standard Acceleo
+ * operation.
+ *
+ * @param operation
+ * Operation which is to be evaluated.
+ * @param source
+ * Source on which the operations is evaluated.
+ * @param args
+ * Arguments of the call.
+ * @return Result of the operation call.
+ */
+ public Object callStandardOperation(EOperation operation, Object source, Object... args) {
+ Object result = null;
+ // Specifications of each standard operation can be found as comments of
+ // AcceleoStandardLibrary#OPERATION_*.
+ if (source instanceof String) {
+ final String sourceValue = (String)source;
+
+ if (AcceleoStandardLibrary.OPERATION_STRING_SUBSTITUTE.equals(operation.getName())) {
+ result = substitute(sourceValue, (String)args[0], (String)args[1], false);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_INDEX.equals(operation.getName())) {
+ result = sourceValue.indexOf((String)args[0]);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_FIRST.equals(operation.getName())) {
+ int endIndex = ((Integer)args[0]).intValue();
+ if (endIndex < 0 || endIndex > sourceValue.length()) {
+ result = sourceValue;
+ } else {
+ result = sourceValue.substring(0, endIndex);
+ }
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_LAST.equals(operation.getName())) {
+ int charCount = ((Integer)args[0]).intValue();
+ if (charCount < 0 || charCount > sourceValue.length()) {
+ result = sourceValue;
+ } else {
+ result = sourceValue.substring(sourceValue.length() - charCount, sourceValue.length());
+ }
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_STRSTR.equals(operation.getName())) {
+ result = sourceValue.contains((String)args[0]);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_STRTOK.equals(operation.getName())) {
+ result = strtok(sourceValue, (String)args[0], (Integer)args[1]);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_STRCMP.equals(operation.getName())) {
+ result = sourceValue.compareTo((String)args[0]);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_ISALPHA.equals(operation.getName())) {
+ result = isAlpha(sourceValue);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_ISALPHANUM.equals(operation.getName())) {
+ result = isAlphanumeric(sourceValue);
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_TOUPPERFIRST.equals(operation.getName())) {
+ if (sourceValue.length() == 0) {
+ result = sourceValue;
+ } else if (sourceValue.length() == 1) {
+ result = sourceValue.toUpperCase();
+ } else {
+ result = Character.toUpperCase(sourceValue.charAt(0)) + sourceValue.substring(1);
+ }
+ } else if (AcceleoStandardLibrary.OPERATION_STRING_TOLOWERFIRST.equals(operation.getName())) {
+ if (sourceValue.length() == 0) {
+ result = sourceValue;
+ } else if (sourceValue.length() == 1) {
+ result = sourceValue.toLowerCase();
+ } else {
+ result = Character.toLowerCase(sourceValue.charAt(0)) + sourceValue.substring(1);
+ }
+ }
+ } else if (source instanceof Integer || source instanceof Long) {
+ if (AcceleoStandardLibrary.OPERATION_INTEGER_TOSTRING.equals(operation.getName())) {
+ result = source.toString();
+ }
+ } else if (source instanceof Double || source instanceof Float) {
+ if (AcceleoStandardLibrary.OPERATION_REAL_TOSTRING.equals(operation.getName())) {
+ result = source.toString();
+ }
+ }
+
+ if (result != null) {
+ return result;
+ }
+
+ // If we're here, the operation is undefined.
+ final StringBuilder argErrorMsg = new StringBuilder();
+ for (int i = 0; i < args.length; i++) {
+ argErrorMsg.append(args[i].getClass().getName());
+ if (i < args.length - 1) {
+ argErrorMsg.append(", "); //$NON-NLS-1$
+ }
+ }
+ final String sourceLabel;
+ if (source == null) {
+ sourceLabel = "null"; //$NON-NLS-1$
+ } else {
+ sourceLabel = source.getClass().getName();
+ }
+ throw new UnsupportedOperationException(AcceleoEngineMessages.getString(
+ "AcceleoEvaluationEnvironment.UndefinedOperation", operation.getName(), argErrorMsg.toString(), //$NON-NLS-1$
+ sourceLabel));
+ }
+
+ /**
+ * This will return the List of all applicable candidates for the given template call with the given
+ * arguments. These will be ordered as described on {@link #reorderCandidatesPriority(Module, Set)}.
+ *
+ * @param origin
+ * Origin of the template call.
+ * @param call
+ * The called element.
+ * @param arguments
+ * Arguments of the call.
+ * @return The set of all applicable templates for these arguments
+ */
+ public List<Template> getAllCandidates(Module origin, Template call, List<Object> arguments) {
+ final List<Object> argumentTypes = new ArrayList<Object>(arguments.size());
+ for (final Object arg : arguments) {
+ if (arg instanceof EObject) {
+ argumentTypes.add(((EObject)arg).eClass());
+ } else {
+ argumentTypes.add(arg.getClass());
+ }
+ }
+
+ /*
+ * NOTE : we depend on the ordering offered by List types. Do not change implementation without
+ * testing.
+ */
+ final List<Template> orderedNamesakes = reorderCandidatesPriority(origin, getAllCandidateNamesakes(
+ call, argumentTypes));
+ final Set<Template> dynamicOverriding = getAllDynamicCandidateOverriding(orderedNamesakes,
+ argumentTypes);
+ final List<Template> overriding = getAllCandidateOverriding(origin, orderedNamesakes, argumentTypes);
+ final List<Template> applicableCandidates = new ArrayList<Template>();
+ // overriding templates come first, then namesakes
+ applicableCandidates.addAll(dynamicOverriding);
+ applicableCandidates.addAll(overriding);
+ applicableCandidates.addAll(orderedNamesakes);
+ return applicableCandidates;
+ }
+
+ /**
+ * Returns the most specific template for the given arguments in the given list.
+ *
+ * @param candidates
+ * List of templates candidates to be substituted.
+ * @param arguments
+ * Arguments of the call.
+ * @return The most specific templates for <code>arguments</code>.
+ */
+ public Template getMostSpecificTemplate(List<Template> candidates, List<Object> arguments) {
+ final Iterator<Template> candidateIterator = candidates.iterator();
+ if (candidates.size() == 1) {
+ return candidateIterator.next();
+ }
+
+ final List<Object> argumentTypes = new ArrayList<Object>(arguments.size());
+ for (final Object arg : arguments) {
+ if (arg instanceof EObject) {
+ argumentTypes.add(((EObject)arg).eClass());
+ } else {
+ argumentTypes.add(arg.getClass());
+ }
+ }
+
+ Template mostSpecific = candidateIterator.next();
+ while (candidateIterator.hasNext()) {
+ mostSpecific = mostSpecificTemplate(mostSpecific, candidateIterator.next(), arguments);
+ }
+ return mostSpecific;
+ }
+
+ /**
+ * Returns a Sequence containing the full set of <code>source</code>'s ancestors.
+ *
+ * @param source
+ * The EObject we seek the ancestors of.
+ * @return Sequence containing the full set of the receiver's ancestors.
+ */
+ private Set<EObject> ancestors(EObject source) {
+ final Set<EObject> result = new LinkedHashSet<EObject>();
+ EObject container = source.eContainer();
+ while (container != null) {
+ result.add(container);
+ container = container.eContainer();
+ }
+ return result;
+ }
+
+ /**
+ * Filters non-applicable templates out of the candidates list.
+ *
+ * @param candidates
+ * List of templates that needs to be filtered out.
+ * @param argumentTypes
+ * Types of the arguments for the call.
+ * @return The set of applicable templates.
+ */
+ private Set<Template> applicableTemplates(Set<Template> candidates, List<Object> argumentTypes) {
+ final Set<Template> applicableCandidates = new LinkedHashSet<Template>(candidates);
+ for (final Template candidate : candidates) {
+ if (candidate.getParameter().size() != argumentTypes.size()) {
+ applicableCandidates.remove(candidate);
+ }
+ }
+ for (int i = 0; i < argumentTypes.size(); i++) {
+ for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
+ final Object parameterType = candidate.getParameter().get(i).getType();
+ if (!isApplicableArgument(parameterType, argumentTypes.get(i))) {
+ applicableCandidates.remove(candidate);
+ }
+ }
+ }
+ return applicableCandidates;
+ }
+
+ /**
+ * Iterates over the content of the given EObject and returns the elements of type <code>filter</code>
+ * from its content tree as a list.
+ *
+ * @param source
+ * The EObject we seek the content tree of.
+ * @param filter
+ * Types of the EObjects we seek to retrieve.
+ * @return The given EObject's whole content tree as a list.
+ */
+ private List<EObject> eAllContents(EObject source, EClassifier filter) {
+ final TreeIterator<EObject> contentIterator = source.eAllContents();
+ final List<EObject> result = new ArrayList<EObject>();
+
+ while (contentIterator.hasNext()) {
+ final EObject next = contentIterator.next();
+ if (filter == null || filter.isInstance(next)) {
+ result.add(next);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a Sequence containing the full set of the inverse references on the receiver.
+ *
+ * @param target
+ * The EObject we seek the inverse references of.
+ * @return Sequence containing the full set of inverse references.
+ */
+ private Set<EObject> eInverse(EObject target) {
+ final Set<EObject> result = new LinkedHashSet<EObject>();
+ if (referencer == null) {
+ if (target.eResource() != null && target.eResource().getResourceSet() != null) {
+ referencer = new CrossReferencer(target.eResource().getResourceSet()) {
+ /** Default SUID. */
+ private static final long serialVersionUID = 1L;
+
+ // static initializer
+ {
+ crossReference();
+ }
+ };
+ } else if (target.eResource() != null) {
+ referencer = new CrossReferencer(target.eResource()) {
+ /** Default SUID. */
+ private static final long serialVersionUID = 1L;
+
+ // static initializer
+ {
+ crossReference();
+ }
+ };
+ } else {
+ referencer = new CrossReferencer(EcoreUtil.getRootContainer(target)) {
+ /** Default SUID. */
+ private static final long serialVersionUID = 1L;
+
+ // static initializer
+ {
+ crossReference();
+ }
+ };
+ }
+ }
+ Collection<EStructuralFeature.Setting> settings = referencer.get(target);
+ if (settings == null) {
+ return Collections.emptySet();
+ }
+ for (Setting setting : settings) {
+ result.add(setting.getEObject());
+ }
+ return result;
+ }
+
+ /**
+ * This will return the list of all namesakes of the template <code>call</code> applicable for
+ * <code>arguments</code>.
+ *
+ * @param call
+ * The called element.
+ * @param argumentTypes
+ * Types of the arguments of the call.
+ * @return All of the applicable templates of this name in the current context.
+ */
+ private Set<Template> getAllCandidateNamesakes(Template call, List<Object> argumentTypes) {
+ final Set<Template> namesakes = new LinkedHashSet<Template>();
+ final Set<Template> candidates = templates.get(call.getName());
+ if (candidates == null) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEvaluationEnvironment.ModuleResolutionError")); //$NON-NLS-1$
+ }
+ namesakes.addAll(candidates);
+ if (namesakes.size() == 1) {
+ return namesakes;
+ }
+ namesakes.retainAll(applicableTemplates(candidates, argumentTypes));
+ return namesakes;
+ }
+
+ /**
+ * This will return the list of all templates overriding one of <code>overridenTemplates</code> that are
+ * applicable for <code>arguments</code>. These will be ordered as specified on
+ * {@link #reorderCandidatesPriority(Module, Set)}.
+ *
+ * @param origin
+ * Origin of the template call.
+ * @param overridenTemplates
+ * List of templates we seek overriding templates of.
+ * @param argumentTypes
+ * Types of the arguments of the call.
+ * @return All of the applicable templates overriding one of <code>overridenTemplates</code> in the
+ * current context.
+ */
+ private List<Template> getAllCandidateOverriding(Module origin, List<Template> overridenTemplates,
+ List<Object> argumentTypes) {
+ final Set<Template> candidateOverriding = new LinkedHashSet<Template>();
+ for (final Template overriden : overridenTemplates) {
+ final Set<Template> candidates = overridingTemplates.get(overriden);
+ if (candidates != null) {
+ final Set<Template> applicableCandidates = applicableTemplates(candidates, argumentTypes);
+ candidateOverriding.addAll(applicableCandidates);
+ // no need to order this, it'll be ordered later on
+ candidateOverriding.addAll(getAllCandidateOverriding(origin, new ArrayList<Template>(
+ applicableCandidates), argumentTypes));
+ }
+ }
+ return reorderCandidatesPriority(origin, candidateOverriding);
+ }
+
+ /**
+ * This will return the list of all templates dynamically overriding one of
+ * <code>overridenTemplates</code> that are applicable for <code>arguments</code>.
+ *
+ * @param overridenTemplates
+ * List of templates we seek overriding templates of.
+ * @param argumentTypes
+ * Types of the arguments of the call.
+ * @return All of the applicable templates dynamically overriding one of <code>overridenTemplates</code>.
+ */
+ private Set<Template> getAllDynamicCandidateOverriding(List<Template> overridenTemplates,
+ List<Object> argumentTypes) {
+ final Set<Template> dynamicOverriding = new LinkedHashSet<Template>();
+ for (final Template overriden : overridenTemplates) {
+ final Set<Template> candidates = dynamicOverrides.get(overriden);
+ if (candidates != null) {
+ final Set<Template> applicableCandidates = applicableTemplates(candidates, argumentTypes);
+ dynamicOverriding.addAll(applicableCandidates);
+ }
+ }
+ return dynamicOverriding;
+ }
+
+ /**
+ * Elements held by a reference with containment=true and derived=true are not returned by
+ * {@link EObject#eContents()}. This allows us to return the list of all contents from an EObject
+ * <u>including</u> those references.
+ *
+ * @param eObject
+ * The EObject we seek the content of.
+ * @return The list of all the content of a given EObject, derived containmnent references included.
+ */
+ @SuppressWarnings("unchecked")
+ private List<EObject> getContents(EObject eObject) {
+ // TODO can this be cached (Map<EClass, List<EReference>>)?
+ final List<EObject> result = new ArrayList<EObject>(eObject.eContents());
+ for (final EReference reference : eObject.eClass().getEAllReferences()) {
+ if (reference.isContainment() && reference.isDerived()) {
+ final Object value = eObject.eGet(reference);
+ if (value instanceof Collection) {
+ result.addAll((Collection)value);
+ } else if (value instanceof EObject) {
+ result.add((EObject)value);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * This will return true if all of the given String's characters are considered letters as per
+ * {@link Character#isLetter(char)}.
+ *
+ * @param s
+ * The String to consider.
+ * @return <code>true</code> if the String is composed of letters only, <code>false</code> otherwise.
+ */
+ private boolean isAlpha(String s) {
+ final char[] chars = s.toCharArray();
+ for (final char c : chars) {
+ if (!Character.isLetter(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This will return true if all of the given String's characters are considered letters or digits as per
+ * {@link Character#isLetterOrDigit(char)}.
+ *
+ * @param s
+ * The String to consider.
+ * @return <code>true</code> if the String is composed of letters and digits only, <code>false</code>
+ * otherwise.
+ */
+ private boolean isAlphanumeric(String s) {
+ final char[] chars = s.toCharArray();
+ for (final char c : chars) {
+ if (!Character.isLetterOrDigit(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns <code>true</code> if the value is applicable to the given type, <code>false</code> otherwise.
+ *
+ * @param expectedType
+ * Expected type of the argument.
+ * @param argumentType
+ * type of the argument we're trying to use as an argument.
+ * @return <code>true</code> if the value is applicable to the given type, <code>false</code> otherwise.
+ */
+ private boolean isApplicableArgument(Object expectedType, Object argumentType) {
+ boolean isApplicable = false;
+ if (expectedType instanceof EClass && argumentType instanceof EClass) {
+ isApplicable = expectedType == argumentType
+ || isSubTypeOf((EClass)expectedType, (EClass)argumentType);
+ } else if (expectedType instanceof Class && argumentType instanceof Class) {
+ isApplicable = ((Class<?>)expectedType).isAssignableFrom((Class<?>)argumentType);
+ } else if (expectedType instanceof EDataType && argumentType instanceof Class) {
+ isApplicable = ((EDataType)expectedType).getInstanceClass() == argumentType;
+ } else {
+ isApplicable = expectedType.getClass().isInstance(argumentType);
+ }
+ return isApplicable;
+ }
+
+ /**
+ * Returns <code>true</code> if <code>eClass</code> is a sub-type of <code>superType</code>,
+ * <code>false</code> otherwise.
+ *
+ * @param superType
+ * Expected super type of <code>eClass</code>.
+ * @param eClass
+ * EClass to consider.
+ * @return <code>true</code> if <code>eClass</code> is a sub-type of <code>superType</code>,
+ * <code>false</code> otherwise.
+ */
+ private boolean isSubTypeOf(EClass superType, EClass eClass) {
+ for (final EClass candidate : eClass.getEAllSuperTypes()) {
+ if (candidate == superType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This will resolve all dependencies of the given module and keep references to all accessible templates.
+ *
+ * @param module
+ * We will resolve dependencies for this module and keep references to all accessible
+ * templates.
+ */
+ private void mapAllTemplates(Module module) {
+ for (final ModuleElement elem : module.getOwnedModuleElement()) {
+ if (elem instanceof Template) {
+ Set<Template> namesakes = templates.get(elem.getName());
+ if (namesakes == null) {
+ namesakes = new LinkedHashSet<Template>();
+ templates.put(elem.getName(), namesakes);
+ }
+ namesakes.add((Template)elem);
+ mapOverridingTemplate((Template)elem);
+ }
+ }
+ for (final Module extended : module.getExtends()) {
+ mapAllTemplates(extended);
+ }
+ for (final Module imported : module.getImports()) {
+ mapAllTemplates(imported);
+ }
+ }
+
+ /**
+ * Maps dynamic overriding templates for smoother polymorphic resolution.
+ */
+ private void mapDynamicOverrides() {
+ for (Module module : AcceleoDynamicTemplatesRegistry.INSTANCE.getRegisteredModules()) {
+ for (final ModuleElement elem : module.getOwnedModuleElement()) {
+ if (elem instanceof Template) {
+ for (final Template overriden : ((Template)elem).getOverrides()) {
+ Set<Template> overriding = dynamicOverrides.get(overriden);
+ if (overriding == null) {
+ overriding = new LinkedHashSet<Template>();
+ Template match = overriden;
+ Set<Template> candidates = templates.get(overriden.getName());
+ for (Template template : candidates) {
+ if (EcoreUtil.equals(template, overriden)) {
+ match = template;
+ break;
+ }
+ }
+ dynamicOverrides.put(match, overriding);
+ }
+ overriding.add((Template)elem);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This will create entries for the given template in the overriding map as needed.
+ *
+ * @param elem
+ * The template which we need to map if overriding.
+ */
+ private void mapOverridingTemplate(Template elem) {
+ for (final Template overriden : elem.getOverrides()) {
+ Set<Template> overriding = overridingTemplates.get(overriden);
+ if (overriding == null) {
+ overriding = new LinkedHashSet<Template>();
+ overridingTemplates.put(overriden, overriding);
+ }
+ overriding.add(elem);
+ }
+ }
+
+ /**
+ * Returns the most specific template of the given two for the given arguments.
+ *
+ * @param template1
+ * First of the two compared templates.
+ * @param template2
+ * Second of the compared templates.
+ * @param actualArgumentTypes
+ * Types of the actual arguments of the call.
+ * @return The most specific templates for <code>actualArgumentTypes</code>.
+ */
+ private Template mostSpecificTemplate(Template template1, Template template2,
+ List<Object> actualArgumentTypes) {
+ Template mostSpecific;
+ // number of arguments which are more specific on template1 as compared to template2
+ int template1SpecificArgumentCount = 0;
+ // ...
+ int template2SpecificArgumentCount = 0;
+ for (int i = 0; i < actualArgumentTypes.size(); i++) {
+ final Object actualArgumentType = actualArgumentTypes.get(i);
+ final EClassifier template1Type = template1.getParameter().get(i).getType();
+ final EClassifier template2Type = template2.getParameter().get(i).getType();
+ if (template1Type == template2Type) {
+ continue;
+ }
+ if (actualArgumentType instanceof EObject) {
+ if (isSubTypeOf((EClass)template1Type, (EClass)template2Type)) {
+ template2SpecificArgumentCount++;
+ } else {
+ template1SpecificArgumentCount++;
+ }
+ } else {
+ // TODO are there any chance the argument would not be an EObject?
+ assert false;
+ }
+ }
+ if (template1SpecificArgumentCount >= template2SpecificArgumentCount) {
+ mostSpecific = template1;
+ } else {
+ mostSpecific = template2;
+ }
+ return mostSpecific;
+ }
+
+ /**
+ * Reorders the <code>candidates</code> list so that templates in the same module as <code>call</code>
+ * come first, then templates in extended modules, and finally templates in imported modules.
+ *
+ * @param origin
+ * The originating module.
+ * @param candidates
+ * List that is to be reordered.
+ * @return The reordered list.
+ */
+ private List<Template> reorderCandidatesPriority(Module origin, Set<Template> candidates) {
+ final List<Template> reorderedList = new ArrayList<Template>(candidates.size());
+
+ for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
+ if (candidate.eContainer() == origin) {
+ reorderedList.add(candidate);
+ candidates.remove(candidate);
+ }
+ }
+
+ for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
+ for (final Module extended : origin.getExtends()) {
+ if (candidate.eContainer() == extended) {
+ reorderedList.add(candidate);
+ candidates.remove(candidate);
+ }
+ }
+ }
+
+ for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
+ for (final Module imported : origin.getImports()) {
+ if (candidate.eContainer() == imported) {
+ reorderedList.add(candidate);
+ candidates.remove(candidate);
+ }
+ }
+ }
+
+ return reorderedList;
+ }
+
+ /**
+ * Returns a Sequence containing the full set of <code>source</code>'s siblings.
+ *
+ * @param source
+ * The EObject we seek the siblings of.
+ * @return Sequence containing the full set of the receiver's siblings.
+ */
+ private Set<EObject> siblings(EObject source) {
+ final Set<EObject> result = new LinkedHashSet<EObject>();
+ EObject container = source.eContainer();
+ for (EObject child : getContents(container)) {
+ if (child != source) {
+ result.add(child);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Implements the Acceleo Standard library "strtok(String, Integer)" operation. This will make use of the
+ * StringTokenizer class and cache the result in order to reuse it as needed.
+ * <p>
+ * <b>Note</b> that this will <b>not</b> fail if called with <code>flag</code> set to <code>1</code> when
+ * no tokens remains in the source String. As the behavior in such a case is not specified by the official
+ * Acceleo specification, we'll return the empty String instead.
+ * </p>
+ *
+ * @param source
+ * Source String in which tokenization has to take place.
+ * @param delimiters
+ * Delimiters around which the <code>source</code> has to be split.
+ * @param flag
+ * The value of this influences the token that needs be returned. A value of <code>0</code>
+ * means the very first token will be returned, a value of <code>1</code> means the returned
+ * token will be the next (as compared to the last one that's been returned).
+ * @return The first of all tokens if <code>flag</code> is <code>0</code>, the next token if
+ * <code>flag</code> is <code>1</code>. Fails in {@link AcceleoEvaluationException} otherwise.
+ */
+ private String strtok(String source, String delimiters, Integer flag) {
+ // flag == 0, create a tokenizer, cache it then return its first element.
+ /*
+ * flag == 1, create the tokenizer if none exists for this source, retrieve the existing one
+ * otherwise. Then returns the next token in it.
+ */
+ if (flag.intValue() == 0) {
+ final StringTokenizer tokenizer = new StringTokenizer(source, delimiters);
+ tokenizers.put(source, tokenizer);
+ return tokenizer.nextToken();
+ } else if (flag.intValue() == 1) {
+ StringTokenizer tokenizer = tokenizers.get(source);
+ if (tokenizer == null) {
+ tokenizer = new StringTokenizer(source, delimiters);
+ tokenizers.put(source, tokenizer);
+ }
+ String token = ""; //$NON-NLS-1$
+ if (tokenizer.hasMoreTokens()) {
+ token = tokenizer.nextToken();
+ }
+ return token;
+ }
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ "AcceleoEvaluationEnvironment.IllegalTokenizerFlag", flag)); //$NON-NLS-1$
+ }
+
+ /**
+ * Implements the Acceleo standard library's "substitute(String, String)" and non-standard library's
+ * "substituteAll(String, String)" operation. It will replace either the first or all occurences of a
+ * given <code>substring</code> in the <code>source</code> by the <code>replacement</code>
+ * String.<b>Neither <code>substring</code> nor <code>replacement</code> are considered regular
+ * expressions.</b>
+ *
+ * @param source
+ * Source String in which the substitution has to take place.
+ * @param substring
+ * Substring which is to be replaced.
+ * @param replacement
+ * String that will be substituted to the sought one.
+ * @param substituteAll
+ * Indicates wheter we should substitute all occurences of the substring or only the first.
+ * @return <code>source</code> with substitution executed, <code>source</code> itself if the substring
+ * hasn't been found.
+ */
+ private String substitute(String source, String substring, String replacement, boolean substituteAll) {
+ if (substring == null || replacement == null) {
+ throw new NullPointerException();
+ }
+ // substitute replaces Strings, not regexes.
+ // Surrounding the regex with \Q [...] \E allows just that
+ final String regex = "\\Q" + substring + "\\E"; //$NON-NLS-1$ //$NON-NLS-2$
+ // We also need to escape backslashes and dollar signs in the replacement (scary!)
+ final String replacementValue = replacement
+ .replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\\\$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+
+ if (substituteAll) {
+ return source.replaceAll(regex, replacementValue);
+ }
+ return source.replaceFirst(regex, replacementValue);
+ }
+
+ /**
+ * Implements the "tokenize" operation on String type. This will return a sequence containing the tokens
+ * of the given string (using <code>delim</code> as delimiter).
+ *
+ * @param source
+ * Source String that is to be tokenized.
+ * @param delim
+ * The delimiters around which the <code>source</code> is to be split.
+ * @return A sequence containing the tokens of the given.
+ */
+ private List<String> tokenize(String source, String delim) {
+ final StringTokenizer tokenizer = new StringTokenizer(source, delim);
+ List<String> result = new ArrayList<String>();
+ while (tokenizer.hasMoreTokens()) {
+ result.add(tokenizer.nextToken());
+ }
+ return result;
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationContext.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationContext.java
new file mode 100644
index 0000000..7e0476e
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationContext.java
@@ -0,0 +1,615 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.internal.evaluation;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.eclipse.acceleo.engine.AcceleoEngineMessages;
+import org.eclipse.acceleo.engine.AcceleoEnginePlugin;
+import org.eclipse.acceleo.engine.AcceleoEvaluationException;
+import org.eclipse.acceleo.engine.event.AcceleoTextGenerationEvent;
+import org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener;
+import org.eclipse.acceleo.model.mtl.Block;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * This will hold all necessary variables for the evaluation of an Acceleo module.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public final class AcceleoEvaluationContext {
+ /** Default size to be used for new buffers. */
+ private static final int DEFAULT_BUFFER_SIZE = 1024;
+
+ /** This pool will be used for the lost file creators. */
+ private static final ExecutorService LOST_CREATORS_POOL = Executors.newFixedThreadPool(Runtime
+ .getRuntime().availableProcessors());
+
+ /** This will be populated with the list of tasks currently executing fot the creation of lost files. */
+ private static final List<Future<Object>> LOST_CREATION_TASKS = new ArrayList<Future<Object>>();
+
+ /**
+ * Blocks' init sections might change existing variables which will need to be restored afterwards. This
+ * will keep the altered variables values.
+ */
+ private final LinkedList<Map<String, Object>> blockVariables = new LinkedList<Map<String, Object>>();
+
+ /** Holds the generation preview in the form of mappings filePath => fileContent. */
+ private final Map<String, StringWriter> generationPreview = new HashMap<String, StringWriter>();
+
+ /** References the file which is to be used as the root for all generated files. */
+ private final File generationRoot;
+
+ /**
+ * This will hold the list of all listeners registered for notification on text generation from this
+ * engine.
+ */
+ private final List<AcceleoTextGenerationListener> listeners = new ArrayList<AcceleoTextGenerationListener>(3);
+
+ /** If <code>true</code>, no file will be generated by this context. */
+ private final boolean previewMode;
+
+ /** This will keep a reference to all user code blocks of a given File. */
+ private final Map<Writer, Map<String, String>> userCodeBlocks = new HashMap<Writer, Map<String, String>>();
+
+ /** This will hold the buffer stack. */
+ private final LinkedList<Writer> writers = new LinkedList<Writer>();
+
+ /**
+ * Instantiates an evaluation context given the root of the to-be-generated files.
+ *
+ * @param root
+ * Root of all files that will be generated.
+ * @param listeners
+ * The list of all listeners that are to be notified for text generation from this context.
+ * @param preview
+ * Tells this evaluation context it shouldn't generate any file.
+ */
+ public AcceleoEvaluationContext(File root, List<AcceleoTextGenerationListener> listeners, boolean preview) {
+ generationRoot = root;
+ previewMode = preview;
+ this.listeners.addAll(listeners);
+ flatten();
+ }
+
+ /**
+ * Allows clients to await for the lost file creation to end.
+ *
+ * @throws InterruptedException
+ * This will be thrown if the lost files creation is interrupted somehow.
+ */
+ public static void awaitCompletion() throws InterruptedException {
+ for (Future<Object> task : new ArrayList<Future<Object>>(LOST_CREATION_TASKS)) {
+ while (!task.isDone() && !task.isCancelled()) {
+ try {
+ task.get();
+ } catch (ExecutionException e) {
+ // LostFileWriters cannot throw exceptions
+ }
+ }
+ LOST_CREATION_TASKS.remove(task);
+ }
+ }
+
+ /**
+ * Appends the given string to the last buffer of the context stack. This will notify all text generation
+ * listeners along the way.
+ *
+ * @param string
+ * String that is to be appended to the current buffer.
+ * @param sourceBlock
+ * The block for which this text has been generated.
+ * @param source
+ * The Object for which was generated this text.
+ * @throws AcceleoEvaluationException
+ * Thrown if we cannot append to the current buffer.
+ */
+ public void append(String string, Block sourceBlock, EObject source) throws AcceleoEvaluationException {
+ try {
+ final Writer currentWriter = writers.getLast();
+ currentWriter.append(string);
+ fireTextGenerated(new AcceleoTextGenerationEvent(string, sourceBlock, source));
+ } catch (final IOException e) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEvaluationContext.AppendException"), e); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Closes the last writer of the stack and returns its result if it was a StringWriter. The empty String
+ * will be returned for FileWriters.
+ *
+ * @return Result held by the last writer of the stack.
+ * @throws AcceleoEvaluationException
+ * This will be thrown if the last writer of the stack cannot be flushed and closed.
+ */
+ public String closeContext() throws AcceleoEvaluationException {
+ final Writer last = writers.removeLast();
+ final String result;
+ try {
+ if (last instanceof AcceleoFileWriter) {
+ // Did we lose user code?
+ final Map<String, String> lostCode = userCodeBlocks.remove(last);
+ if (lostCode.size() > 0) {
+ createLostFile(((AcceleoFileWriter)last).getTargetPath(), lostCode);
+ }
+ // Add a carriage return at the end of each file so that no problem will arise with
+ // indentation when appending
+ last.append('\n');
+ last.close();
+ result = ""; //$NON-NLS-1$
+ } else if (last instanceof OutputStreamWriter) {
+ last.close();
+ result = ""; //$NON-NLS-1$
+ } else if (previewMode) {
+ for (final Entry<String, StringWriter> entry : generationPreview.entrySet()) {
+ if (entry.getValue() == last) {
+ final Map<String, String> lostCode = userCodeBlocks.remove(last);
+ if (lostCode.size() > 0) {
+ StringWriter lostContent = new StringWriter();
+ for (final String lostAreaContent : lostCode.values()) {
+ lostContent.append(lostAreaContent);
+ lostContent.append('\n');
+ }
+ generationPreview.put(entry.getKey().concat(".lost"), lostContent); //$NON-NLS-1$
+ }
+ }
+ }
+ result = last.toString();
+ } else {
+ // others are plain StringWriters. Close has no effect on those.
+ result = last.toString();
+ }
+ return result;
+ } catch (final IOException e) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoEvaluationContext.WriteError"), //$NON-NLS-1$
+ e);
+ }
+ }
+
+ /**
+ * This will be used to dispose of all created buffers and caches.
+ *
+ * @throws AcceleoEvaluationException
+ * Thrown if the disposal of the old writers fails.
+ */
+ public void dispose() throws AcceleoEvaluationException {
+ AcceleoEvaluationException exception = null;
+ try {
+ try {
+ for (final Writer writer : writers) {
+ writer.close();
+ }
+ } catch (final IOException e) {
+ exception = new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEvaluationContext.CleanUpError"), e); //$NON-NLS-1$
+ }
+ try {
+ awaitCompletion();
+ } catch (InterruptedException e) {
+ exception = new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEvaluationContext.CleanUpError"), e); //$NON-NLS-1$
+ }
+ } finally {
+ writers.clear();
+ blockVariables.clear();
+ userCodeBlocks.clear();
+ listeners.clear();
+ generationPreview.clear();
+ LOST_CREATION_TASKS.clear();
+ }
+ if (exception != null) {
+ throw exception;
+ }
+ }
+
+ /**
+ * Returns the preview of the generation handled by this context.
+ *
+ * @return The generation preview.
+ */
+ public Map<String, StringWriter> getGenerationPreview() {
+ return generationPreview;
+ }
+
+ /**
+ * This will return the last variables that were added to the stack so that they can be restored in the
+ * evaluation environment. Note that calling this method removes the returned variables from the stack.
+ *
+ * @return The variables that were last saved.
+ */
+ public Map<String, Object> getLastVariablesValues() {
+ return blockVariables.removeLast();
+ }
+
+ /**
+ * This will return the content of the protected area associated with the given marker in the current
+ * context.
+ *
+ * @param marker
+ * Marker of the sought protected area content.
+ * @return Content of the protected area associated with the given marker. <code>null</code> if no content
+ * can be found.
+ */
+ public String getProtectedAreaContent(String marker) {
+ // Seeks out the last opened file writer
+ Writer writer = null;
+ for (int i = writers.size() - 1; i >= 0; i--) {
+ writer = writers.get(i);
+ if (writer instanceof AcceleoFileWriter) {
+ break;
+ }
+ writer = null;
+ }
+
+ final Map<String, String> areas = userCodeBlocks.get(writer);
+ if (areas != null) {
+ return areas.remove(marker);
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new writer and appends it to the end of the stack.
+ *
+ * @throws AcceleoEvaluationException
+ * Thrown if the precedent buffer cannot be flushed.
+ */
+ public void openNested() throws AcceleoEvaluationException {
+ try {
+ if (writers.size() > 0) {
+ writers.getLast().flush();
+ }
+ } catch (final IOException e) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoEvaluationContext.FlushError"), //$NON-NLS-1$
+ e);
+ }
+ writers.add(new StringWriter(DEFAULT_BUFFER_SIZE));
+ }
+
+ /**
+ * Create a new writer directed at the given {@link OutputStream}. This is mainly used for fileBlocks with
+ * "stdout" URI.
+ *
+ * @param stream
+ * Stream to which writing will be directed.
+ */
+ public void openNested(OutputStream stream) {
+ try {
+ if (writers.size() > 0) {
+ writers.getLast().flush();
+ }
+ } catch (final IOException e) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoEvaluationContext.FlushError"), //$NON-NLS-1$
+ e);
+ }
+ writers.add(new OutputStreamWriter(new AcceleoFilterOutputStream(stream)));
+ }
+
+ /**
+ * Create a new writer for the file located at the given path under <tt>generationRoot</tt> and appends it
+ * to the end of the stack.
+ * <p>
+ * &quot;file&quot; schemes are handled as absolute paths and will ignore the <tt>generationRoot</tt>.
+ * </p>
+ *
+ * @param filePath
+ * Path of the file around which we need a FileWriter. The file will be created under the
+ * generationRoot if needed.
+ * @param fileBlock
+ * The file block which asked for this context. Only used for generation events.
+ * @param source
+ * The source EObject for this file block. Only used for generation events.
+ * @param appendMode
+ * If <code>false</code>, the file will be replaced by a new one.
+ * @throws AcceleoEvaluationException
+ * Thrown if the file cannot be created.
+ */
+ public void openNested(String filePath, Block fileBlock, EObject source, boolean appendMode)
+ throws AcceleoEvaluationException {
+ final File generatedFile;
+ if (filePath.startsWith("file:")) { //$NON-NLS-1$
+ generatedFile = new File(filePath);
+ } else {
+ generatedFile = new File(generationRoot, filePath);
+ }
+ fireFilePathComputed(new AcceleoTextGenerationEvent(generatedFile.getPath(), fileBlock, source));
+ if (!previewMode && !generatedFile.getParentFile().exists()) {
+ if (!generatedFile.getParentFile().mkdirs()) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ "AcceleoEvaluationContext.FolderCreationError", generatedFile.getParentFile())); //$NON-NLS-1$
+ }
+ }
+ try {
+ writers.getLast().flush();
+ Map<String, String> savedCodeBlocks = new HashMap<String, String>();
+ if (generatedFile.exists()) {
+ savedCodeBlocks = saveProtectedAreas(generatedFile);
+ }
+ final Writer writer;
+ if (!previewMode) {
+ writer = new AcceleoFileWriter(generatedFile, appendMode);
+ } else {
+ writer = new StringWriter();
+ generationPreview.put(generatedFile.getPath(), (StringWriter)writer);
+ }
+ userCodeBlocks.put(writer, savedCodeBlocks);
+ writers.add(writer);
+ } catch (final IOException e) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ "AcceleoEvaluationContext.FileCreationError", generatedFile.getPath()), e); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This will save the given variables in the stack.
+ *
+ * @param vars
+ * Variables which values will need to be restored after evaluation.
+ */
+ public void saveVariableValues(Map<String, Object> vars) {
+ blockVariables.add(vars);
+ }
+
+ /**
+ * Creates a .lost file that will contain the lost user code from file located at
+ * <code>originalPath</code>. As it doesn't need any more computation, the lost file creation will take
+ * place in a separate process.
+ *
+ * @param originalPath
+ * Absolute path of the file which user code has been fully or partially lost.
+ * @param lostAreas
+ * Protected areas which markers couldn't be matched with markers from the template file.
+ */
+ private void createLostFile(final String originalPath, final Map<String, String> lostAreas) {
+ final Callable<Object> fileCreator = new LostFileWriter(originalPath, lostAreas);
+ LOST_CREATION_TASKS.add(LOST_CREATORS_POOL.submit(fileCreator));
+ }
+
+ /**
+ * Used internally to remove ended tasks from the cache.
+ */
+ private void flatten() {
+ for (Future<Object> task : new ArrayList<Future<Object>>(LOST_CREATION_TASKS)) {
+ if (task.isDone() && task.isCancelled()) {
+ LOST_CREATION_TASKS.remove(task);
+ }
+ }
+ }
+
+ /**
+ * Notifies all listeners that a file is going to be created.
+ *
+ * @param event
+ * The generation event that is to be sent to registered listeners.
+ */
+ private void fireFilePathComputed(AcceleoTextGenerationEvent event) {
+ for (AcceleoTextGenerationListener listener : listeners) {
+ listener.filePathComputed(event);
+ }
+ }
+
+ /**
+ * Notifies all listeners that text has been generated.
+ *
+ * @param event
+ * The generation event that is to be sent to registered listeners.
+ */
+ private void fireTextGenerated(AcceleoTextGenerationEvent event) {
+ for (AcceleoTextGenerationListener listener : listeners) {
+ listener.textGenerated(event);
+ }
+ }
+
+ /**
+ * This will return the list of protected areas the given file contains.
+ *
+ * @param file
+ * File which protected areas are to be saved.
+ * @return The list of saved protected areas.
+ * @throws IOException
+ * Thrown if we cannot read through <tt>file</tt>.
+ */
+ private Map<String, String> saveProtectedAreas(File file) throws IOException {
+ final Map<String, String> protectedAreas = new HashMap<String, String>();
+ BufferedReader reader = null;
+ try {
+ final String usercodeStart = AcceleoEngineMessages.getString("usercode.start"); //$NON-NLS-1$
+ final String usercodeEnd = AcceleoEngineMessages.getString("usercode.end"); //$NON-NLS-1$
+
+ reader = new BufferedReader(new FileReader(file));
+ String line = reader.readLine();
+ while (line != null) {
+ if (line.contains(usercodeStart)) {
+ final String marker = line
+ .substring(line.indexOf(usercodeStart) + usercodeStart.length()).trim();
+ final StringBuffer areaContent = new StringBuffer('\n');
+ // Everything preceding the start of user code doesn't need to be saved
+ areaContent.append(line.substring(line.indexOf(usercodeStart)));
+ line = reader.readLine();
+ while (line != null) {
+ areaContent.append('\n');
+ // Everything following the end of use code marker doesn't need to be saved
+ if (line.contains(usercodeEnd)) {
+ areaContent.append(line.substring(0, line.indexOf(usercodeEnd)
+ + usercodeEnd.length()));
+ break;
+ }
+ areaContent.append(line);
+ line = reader.readLine();
+ }
+ areaContent.append('\n');
+ protectedAreas.put(marker, areaContent.toString());
+ }
+ line = reader.readLine();
+ }
+ } catch (final FileNotFoundException e) {
+ // cannot be thrown here, we were called after testing that the file indeed existed.
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ return protectedAreas;
+ }
+
+ /**
+ * This will be used to create log files.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+ private final class LostFileWriter implements Callable<Object> {
+ /** Lost protected areas. */
+ private final Map<String, String> lostAreas;
+
+ /** Path to the file which protected areas have been lost. */
+ private final String originalPath;
+
+ /**
+ * Instantiate a writer given the path to the original file (will be suffixed by &quot;.lost&quot;)
+ * and a map containing the lost protected areas.
+ *
+ * @param originalPath
+ * Path to the file in which protected areas have been lost.
+ * @param lostAreas
+ * Map containing the lost protected areas of this file.
+ */
+ LostFileWriter(String originalPath, Map<String, String> lostAreas) {
+ this.originalPath = originalPath;
+ this.lostAreas = lostAreas;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Runnable#run()
+ */
+ public Object call() {
+ StringBuilder lostContent = new StringBuilder();
+ for (final String lostAreaContent : lostAreas.values()) {
+ lostContent.append(lostAreaContent);
+ lostContent.append('\n');
+ }
+ Writer writer = null;
+ try {
+ final File lostFile = new File(originalPath.concat(".lost")); //$NON-NLS-1$
+ writer = new BufferedWriter(new FileWriter(lostFile, true));
+ writer.append('\n').append(Calendar.getInstance().getTime().toString()).append('\n');
+ writer
+ .append("================================================================================"); //$NON-NLS-1$
+ writer.append('\n');
+ writer.append(lostContent);
+ } catch (final IOException e) {
+ final String errorMessage = AcceleoEngineMessages.getString(
+ "AcceleoEvaluationContext.LostContent", originalPath, lostContent); //$NON-NLS-1$
+ AcceleoEnginePlugin.log(errorMessage, false);
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ AcceleoEnginePlugin.log(e, false);
+ }
+ }
+ }
+ // This has no explicit result. Only used to await termination
+ return null;
+ }
+ }
+
+ /**
+ * This implementation of a BufferedWriter will be wrapped around a FileWriter and keep a reference to its
+ * target file's absolute path.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+ private final class AcceleoFileWriter extends BufferedWriter {
+ /** Keeps a reference to the target file's absolute path. */
+ private final String targetPath;
+
+ /**
+ * Constructs a buffered file writer around the given file.
+ *
+ * @param target
+ * File in which this writer will append text.
+ * @param appendMode
+ * Tells us wether the former content of the file should be deleted.
+ * @throws IOException
+ * Thrown if the target file doesn't exist and cannot be created.
+ */
+ public AcceleoFileWriter(File target, boolean appendMode) throws IOException {
+ super(new FileWriter(target, appendMode));
+ targetPath = target.getAbsolutePath();
+ }
+
+ public String getTargetPath() {
+ return targetPath;
+ }
+ }
+
+ /**
+ * This implementation of a FilterOutputStream will avoid closing the standard output if it is the
+ * underlying stream.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+ private final class AcceleoFilterOutputStream extends FilterOutputStream {
+ /**
+ * Constructs an output stream redirecting all calls to the given {@link OutputStream}.
+ *
+ * @param out
+ * The decorated output stream.
+ */
+ public AcceleoFilterOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.io.FilterOutputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ flush();
+ } catch (IOException e) {
+ // Ignored exception
+ }
+ if (out != System.out) {
+ out.close();
+ }
+ }
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationVisitor.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationVisitor.java
new file mode 100644
index 0000000..236f739
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/evaluation/AcceleoEvaluationVisitor.java
@@ -0,0 +1,948 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.internal.evaluation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.acceleo.engine.AcceleoEngineMessages;
+import org.eclipse.acceleo.engine.AcceleoEvaluationException;
+import org.eclipse.acceleo.engine.internal.debug.ASTFragment;
+import org.eclipse.acceleo.engine.internal.debug.IDebugAST;
+import org.eclipse.acceleo.engine.internal.environment.AcceleoEvaluationEnvironment;
+import org.eclipse.acceleo.model.mtl.Block;
+import org.eclipse.acceleo.model.mtl.FileBlock;
+import org.eclipse.acceleo.model.mtl.ForBlock;
+import org.eclipse.acceleo.model.mtl.IfBlock;
+import org.eclipse.acceleo.model.mtl.InitSection;
+import org.eclipse.acceleo.model.mtl.LetBlock;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.acceleo.model.mtl.MtlFactory;
+import org.eclipse.acceleo.model.mtl.MtlPackage;
+import org.eclipse.acceleo.model.mtl.OpenModeKind;
+import org.eclipse.acceleo.model.mtl.ProtectedAreaBlock;
+import org.eclipse.acceleo.model.mtl.Query;
+import org.eclipse.acceleo.model.mtl.QueryInvocation;
+import org.eclipse.acceleo.model.mtl.Template;
+import org.eclipse.acceleo.model.mtl.TemplateInvocation;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.ocl.EvaluationVisitor;
+import org.eclipse.ocl.EvaluationVisitorDecorator;
+import org.eclipse.ocl.ecore.StringLiteralExp;
+import org.eclipse.ocl.ecore.Variable;
+import org.eclipse.ocl.expressions.OCLExpression;
+import org.eclipse.ocl.expressions.OperationCallExp;
+import org.eclipse.ocl.expressions.PropertyCallExp;
+
+/**
+ * This visitor will handle the evaluation of Acceleo nodes.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ * @param <PK>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <C>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <O>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <P>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <EL>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <PM>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <S>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <COA>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <SSA>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <CT>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <CLS>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ * @param <E>
+ * see {@link #org.eclipse.ocl.AbstractEvaluationVisitor}.
+ */
+public class AcceleoEvaluationVisitor<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> extends EvaluationVisitorDecorator<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> {
+ /** Externalized name of the "self" OCL variable to avoid too many distinct uses. */
+ private static final String SELF_VARIABLE_NAME = "self"; //$NON-NLS-1$
+
+ /** Key of the "undefined guard" error message in acceleoenginemessages.properties. */
+ private static final String UNDEFINED_GUARD_MESSAGE_KEY = "AcceleoEvaluationVisitor.UndefinedGuard"; //$NON-NLS-1$
+
+ /**
+ * To debug an AST evaluation. TODO JMU : Put this debugger instance in the evaluation context
+ */
+ private static IDebugAST debug;
+
+ /**
+ * A query returns the same result each time it is called with the same arguments. This map will allow us
+ * to keep the result in cache for faster subsequent calls.
+ */
+ private final Map<Query, Map<List<Object>, Object>> queryResults = new HashMap<Query, Map<List<Object>, Object>>();
+
+ /** Generation context of this visitor. */
+ private final AcceleoEvaluationContext context;
+
+ /** This will allow us to remember the last EObject value of the self variable. */
+ private EObject lastEObjectSelfValue;
+
+ /**
+ * This will be set before each call to a PropertyCallExpression or OperationCallExpression and will allow
+ * us to determine the source of such or such call.
+ */
+ private OCLExpression<C> lastSourceExpression;
+
+ /** Keeps track of the result of the last source expression. */
+ private Object lastSourceExpressionResult;
+
+ /** Retrieve OCL_Invalid once and for all. */
+ private final Object oclInvalid = getEnvironment().getOCLStandardLibrary().getOclInvalid();
+
+ /**
+ * Default constructor.
+ *
+ * @param decoratedVisitor
+ * The evaluation visitor this instance will decorate.
+ * @param context
+ * The evaluation context this visitor has to take into account.
+ */
+ public AcceleoEvaluationVisitor(
+ EvaluationVisitor<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> decoratedVisitor,
+ AcceleoEvaluationContext context) {
+ super(decoratedVisitor);
+ this.context = context;
+ }
+
+ /**
+ * To debug an AST evaluation.
+ *
+ * @param acceleoDebug
+ * is the new debugger to consider
+ */
+ public static void setDebug(IDebugAST acceleoDebug) {
+ debug = acceleoDebug;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.AbstractEvaluationVisitor#visitExpression(org.eclipse.ocl.expressions.OCLExpression)
+ */
+ @Override
+ public Object visitExpression(OCLExpression<C> expression) {
+ Object result = null;
+ EObject debugInput = null;
+ ASTFragment astFragment = null;
+ if (debug != null && !(expression instanceof StringLiteralExp)) {
+ // TODO JMU see new object "lastSourceExpressionResult". this could be interesting.
+ debugInput = lastEObjectSelfValue;
+ astFragment = new ASTFragment(expression);
+ if (debugInput != null && debugInput.eClass().getEStructuralFeature("name") != null) { //$NON-NLS-1$
+ Object name = debugInput.eGet(debugInput.eClass().getEStructuralFeature("name")); //$NON-NLS-1$
+ if (name instanceof String) {
+ astFragment.setEObjectNameFilter((String)name);
+ }
+ }
+ debug.startDebug(astFragment);
+ debug.stepDebugInput(astFragment, debugInput);
+ }
+ // This try / catch block allows us to handle the disposal of all context information.
+ try {
+ // All evaluations pass through here. We'll handle blocks' init sections here.
+ final boolean hasInit = expression instanceof Block && ((Block)expression).getInit() != null;
+ if (hasInit) {
+ handleAcceleoInitSection(((Block)expression).getInit());
+ }
+ // Actual delegation to the visitor's methods.
+ if (expression instanceof Template) {
+ result = visitAcceleoTemplate((Template)expression);
+ } else if (expression instanceof IfBlock) {
+ visitAcceleoIfBlock((IfBlock)expression);
+ // This has no explicit result
+ result = ""; //$NON-NLS-1$
+ } else if (expression instanceof ForBlock) {
+ visitAcceleoForBlock((ForBlock)expression);
+ // This has no explicit result
+ result = ""; //$NON-NLS-1$
+ } else if (expression instanceof FileBlock) {
+ visitAcceleoFileBlock((FileBlock)expression);
+ // This has no explicit result
+ result = ""; //$NON-NLS-1$
+ } else if (expression instanceof TemplateInvocation) {
+ result = visitAcceleoTemplateInvocation((TemplateInvocation)expression);
+ } else if (expression instanceof QueryInvocation) {
+ result = visitAcceleoQueryInvocation((QueryInvocation)expression);
+ } else if (expression instanceof LetBlock) {
+ visitAcceleoLetBlock((LetBlock)expression);
+ // This has no explicit result
+ result = ""; //$NON-NLS-1$
+ } else if (expression instanceof ProtectedAreaBlock) {
+ visitAcceleoProtectedArea((ProtectedAreaBlock)expression);
+ // This has no explicit result
+ result = ""; //$NON-NLS-1$
+ } else {
+ result = super.visitExpression(expression);
+ }
+
+ if (expression == lastSourceExpression) {
+ lastSourceExpressionResult = result;
+ }
+
+ if (shouldGenerateText((EReference)expression.eContainingFeature())) {
+ Object source = null;
+ // TODO get last structural feature
+ EObject generatedBlock = expression;
+ while (!(generatedBlock instanceof Block)) {
+ generatedBlock = generatedBlock.eContainer();
+ }
+ if (lastSourceExpressionResult == null) {
+ source = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ } else {
+ source = lastSourceExpressionResult;
+ lastSourceExpressionResult = null;
+ }
+ if (source instanceof EObject) {
+ lastEObjectSelfValue = (EObject)source;
+ }
+ context.append(String.valueOf(result), (Block)generatedBlock, lastEObjectSelfValue);
+ }
+
+ // If we were evaluating a block and it had an init section, restore variables as they were now.
+ if (hasInit) {
+ restoreVariables();
+ }
+ } catch (final AcceleoEvaluationException e) {
+ throw e;
+ // CHECKSTYLE:OFF
+ /*
+ * deactivated checkstyle as we need to properly dispose the context when an exception is throw
+ * yet we cannot use finally (this visitor is called recursively).
+ */
+ } catch (final RuntimeException e) {
+ // CHECKSTYLE:ON
+ try {
+ context.dispose();
+ } catch (final AcceleoEvaluationException ee) {
+ // We're already in an exception handling phase. Propagate the former exception.
+ }
+ throw e;
+ } finally {
+ if (debug != null && !(expression instanceof StringLiteralExp)) {
+ debug.stepDebugOutput(astFragment, debugInput, result);
+ debug.endDebug(astFragment);
+ }
+ }
+ // FIXME check if OCL_Invalid results are logged by OCL
+ // FIXME handle exceptions (should probably add runtime error markers)
+
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.EvaluationVisitorDecorator#visitPropertyCallExp(org.eclipse.ocl.expressions.PropertyCallExp)
+ */
+ @Override
+ public Object visitPropertyCallExp(PropertyCallExp<C, P> callExp) {
+ lastSourceExpression = callExp.getSource();
+ return super.visitPropertyCallExp(callExp);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ocl.EvaluationVisitorDecorator#visitOperationCallExp(org.eclipse.ocl.expressions.OperationCallExp)
+ */
+ @Override
+ public Object visitOperationCallExp(OperationCallExp<C, O> callExp) {
+ lastSourceExpression = callExp.getSource();
+ return super.visitOperationCallExp(callExp);
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link Block}.
+ *
+ * @param block
+ * The Acceleo block that is to be evaluated.
+ */
+ @SuppressWarnings("unchecked")
+ public void visitAcceleoBlock(Block block) {
+ for (final OCLExpression nested : block.getBody()) {
+ visitExpression(nested);
+ }
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link FileBlock}.
+ *
+ * @param fileBlock
+ * The file block that need be evaluated.
+ */
+ @SuppressWarnings("unchecked")
+ public void visitAcceleoFileBlock(FileBlock fileBlock) {
+ final Object fileURLResult = visitExpression((OCLExpression)fileBlock.getFileUrl());
+ if (isUndefined(fileURLResult)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedFileURL", fileBlock.getStartPosition(), ((Module)EcoreUtil.getRootContainer(fileBlock)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+ final String filePath = String.valueOf(fileURLResult);
+ final boolean appendMode = fileBlock.getOpenMode().getValue() == OpenModeKind.APPEND_VALUE;
+ if ("stdout".equals(filePath)) { //$NON-NLS-1$
+ context.openNested(System.out);
+ } else {
+ final Object currentSelf = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ final EObject source;
+ if (currentSelf instanceof EObject) {
+ source = (EObject)currentSelf;
+ } else {
+ source = lastEObjectSelfValue;
+ }
+ context.openNested(filePath, fileBlock, source, appendMode);
+ }
+ // TODO handle file ID
+ for (final OCLExpression nested : fileBlock.getBody()) {
+ visitExpression(nested);
+ }
+ context.closeContext();
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link ForBlock}.
+ *
+ * @param forBlock
+ * The Acceleo block that is to be evaluated.
+ */
+ @SuppressWarnings("unchecked")
+ public void visitAcceleoForBlock(ForBlock forBlock) {
+ final Object iteration = visitExpression((OCLExpression)forBlock.getIterSet());
+ final Variable loopVariable = forBlock.getLoopVariable();
+ final Object currentSelf = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+
+ if (isUndefined(iteration)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ "AcceleoEvaluationVisitor.NullForIteration", forBlock.getStartPosition(), ((Module)EcoreUtil //$NON-NLS-1$
+ .getRootContainer(forBlock)).getName()));
+ exception.fillInStackTrace();
+ throw exception;
+ }
+
+ // There is a possibility for the for to have a single element in its iteration
+ if (iteration instanceof Collection) {
+ if (((Collection)iteration).size() > 0 && forBlock.getBefore() != null) {
+ visitExpression((OCLExpression)forBlock.getBefore());
+ }
+ final Iterator<Object> contentIterator = ((Collection)iteration).iterator();
+ while (contentIterator.hasNext()) {
+ final Object o = contentIterator.next();
+ getEvaluationEnvironment().replace(loopVariable.getName(), o);
+ // [255379] sets new value of "self" to change context
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, o);
+
+ final Object guardValue;
+ if (forBlock.getGuard() == null) {
+ guardValue = Boolean.TRUE;
+ } else {
+ guardValue = visitExpression((OCLExpression)forBlock.getGuard());
+ }
+ if (isUndefined(guardValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString(UNDEFINED_GUARD_MESSAGE_KEY, forBlock.getStartPosition(),
+ ((Module)EcoreUtil.getRootContainer(forBlock)).getName()));
+ exception.fillInStackTrace();
+ throw exception;
+ }
+
+ if (((Boolean)guardValue).booleanValue()) {
+ for (final OCLExpression nested : forBlock.getBody()) {
+ visitExpression(nested);
+ }
+ if (forBlock.getEach() != null && contentIterator.hasNext()) {
+ visitExpression((OCLExpression)forBlock.getEach());
+ }
+ }
+ }
+ if (((Collection)iteration).size() > 0 && forBlock.getAfter() != null) {
+ visitExpression((OCLExpression)forBlock.getAfter());
+ }
+ } else {
+ if (forBlock.getBefore() != null) {
+ visitExpression((OCLExpression)forBlock.getBefore());
+ }
+ getEvaluationEnvironment().replace(loopVariable.getName(), iteration);
+ // [255379] sets new value of "self" to change context
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, iteration);
+
+ final Object guardValue;
+ if (forBlock.getGuard() == null) {
+ guardValue = Boolean.TRUE;
+ } else {
+ guardValue = visitExpression((OCLExpression)forBlock.getGuard());
+ }
+ if (isUndefined(guardValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString(UNDEFINED_GUARD_MESSAGE_KEY, forBlock.getStartPosition(),
+ ((Module)EcoreUtil.getRootContainer(forBlock)).getName()));
+ exception.fillInStackTrace();
+ throw exception;
+ }
+
+ if (((Boolean)guardValue).booleanValue()) {
+ for (final OCLExpression nested : forBlock.getBody()) {
+ visitExpression(nested);
+ }
+ }
+ if (forBlock.getAfter() != null) {
+ visitExpression((OCLExpression)forBlock.getAfter());
+ }
+ }
+
+ // [255379] restore context
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, currentSelf);
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link IfBlock}.
+ *
+ * @param ifBlock
+ * The Acceleo block that is to be evaluated.
+ */
+ @SuppressWarnings("unchecked")
+ public void visitAcceleoIfBlock(IfBlock ifBlock) {
+ final OCLExpression condition = ifBlock.getIfExpr();
+
+ final Object conditionValue = visitExpression(condition);
+ if (isUndefined(conditionValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedCondition", ifBlock.getStartPosition(), ((Module)EcoreUtil.getRootContainer(ifBlock)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+
+ if (((Boolean)conditionValue).booleanValue()) {
+ for (final OCLExpression nested : ifBlock.getBody()) {
+ visitExpression(nested);
+ }
+ } else {
+ if (ifBlock.getElseIf().size() > 0) {
+ // If one of the else ifs has its condition evaluated to true, this will hold it
+ IfBlock temp = null;
+ for (final IfBlock elseif : ifBlock.getElseIf()) {
+ final Object elseValue = visitExpression((OCLExpression)elseif.getIfExpr());
+ if (isUndefined(elseValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedElseCondition", elseif.getStartPosition(), ((Module)EcoreUtil.getRootContainer(elseif)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+ if (((Boolean)elseValue).booleanValue()) {
+ temp = elseif;
+ break;
+ }
+ }
+ if (temp != null) {
+ for (final OCLExpression nested : temp.getBody()) {
+ visitExpression(nested);
+ }
+ } else if (ifBlock.getElse() != null) {
+ visitAcceleoBlock(ifBlock.getElse());
+ }
+ } else if (ifBlock.getElse() != null) {
+ visitAcceleoBlock(ifBlock.getElse());
+ }
+ }
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link LetBlock}.
+ *
+ * @param letBlock
+ * The Acceleo let block that is to be evaluated.
+ */
+ @SuppressWarnings("unchecked")
+ public void visitAcceleoLetBlock(LetBlock letBlock) {
+ Variable var = letBlock.getLetVariable();
+ Object value = visitExpression((OCLExpression)var.getInitExpression());
+ if (isUndefined(value)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedLetValue", letBlock.getStartPosition(), ((Module)EcoreUtil.getRootContainer(letBlock)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+
+ if (var.getType().isInstance(value)) {
+ getEvaluationEnvironment().replace(var.getName(), value);
+ for (final OCLExpression nested : letBlock.getBody()) {
+ visitExpression(nested);
+ }
+ } else {
+ if (letBlock.getElseLet().size() > 0) {
+ // If one of the else lets has its "instanceof" condition evaluated to true, this will hold it
+ LetBlock temp = null;
+ for (final LetBlock elseLet : letBlock.getElseLet()) {
+ var = elseLet.getLetVariable();
+ value = visitExpression((OCLExpression)var.getInitExpression());
+ if (isUndefined(value)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedElseLetValue", elseLet.getStartPosition(), ((Module)EcoreUtil.getRootContainer(elseLet)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+ if (var.getType().isInstance(value)) {
+ getEvaluationEnvironment().replace(var.getName(), value);
+ temp = elseLet;
+ break;
+ }
+ }
+ if (temp != null) {
+ for (final OCLExpression nested : temp.getBody()) {
+ visitExpression(nested);
+ }
+ } else if (letBlock.getElse() != null) {
+ visitAcceleoBlock(letBlock.getElse());
+ }
+ } else if (letBlock.getElse() != null) {
+ visitAcceleoBlock(letBlock.getElse());
+ }
+ }
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link ProtectedAreaBlock}.
+ *
+ * @param protectedArea
+ * The Acceleo protected area that is to be evaluated.
+ */
+ @SuppressWarnings("unchecked")
+ public void visitAcceleoProtectedArea(ProtectedAreaBlock protectedArea) {
+ final Object markerValue = visitExpression((OCLExpression)protectedArea.getMarker());
+ if (isUndefined(markerValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedAreaMarker", protectedArea.getStartPosition(), ((Module)EcoreUtil.getRootContainer(protectedArea)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+
+ final String marker = markerValue.toString().trim();
+ final String areaContent = context.getProtectedAreaContent(marker);
+ final Object source = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ if (source instanceof EObject) {
+ lastEObjectSelfValue = (EObject)source;
+ }
+ if (areaContent != null) {
+ context.append(areaContent, protectedArea, lastEObjectSelfValue);
+ } else {
+ context
+ .append(
+ AcceleoEngineMessages.getString("usercode.start"), protectedArea, lastEObjectSelfValue); //$NON-NLS-1$
+ context.append(' ' + marker, protectedArea, lastEObjectSelfValue);
+ visitAcceleoBlock(protectedArea);
+ context.append(AcceleoEngineMessages.getString("usercode.end"), protectedArea, lastEObjectSelfValue); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link QueryInvocation}.
+ *
+ * @param invocation
+ * The Acceleo query invocation that is to be evaluated.
+ * @return result of the invocation.
+ */
+ @SuppressWarnings("unchecked")
+ public Object visitAcceleoQueryInvocation(QueryInvocation invocation) {
+ final Query query = invocation.getDefinition();
+
+ final List<Object> arguments = new ArrayList<Object>();
+ for (int i = 0; i < query.getParameter().size(); i++) {
+ final Object argValue = visitExpression((OCLExpression)invocation.getArgument().get(i));
+ if (isUndefined(argValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEvaluationVisitor.UndefinedArgument", invocation.getStartPosition(), //$NON-NLS-1$
+ ((Module)EcoreUtil.getRootContainer(invocation)).getName()));
+ exception.fillInStackTrace();
+ throw exception;
+ }
+ arguments.add(argValue);
+ }
+ // If the query has already been run with these arguments, return the cached result
+ if (queryResults.containsKey(query)) {
+ final Map<List<Object>, Object> results = queryResults.get(query);
+ final Object result = results.get(arguments);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ // sets or replaces parameters for the invocation
+ // [255379] size + 1 to keep old value of "self"
+ final Object[] oldArgs = new Object[invocation.getArgument().size() + 1];
+ for (int i = 0; i < query.getParameter().size(); i++) {
+ final Variable param = query.getParameter().get(i);
+ oldArgs[i] = getEvaluationEnvironment().getValueOf(param.getName());
+ getEvaluationEnvironment().replace(param.getName(), arguments.get(i));
+ // [255379] sets new value of "self" to match the very first arg of the query
+ if (i == 0) {
+ oldArgs[oldArgs.length - 1] = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, arguments.get(i));
+ }
+ }
+
+ final Object result = visitExpression((OCLExpression)query.getExpression());
+
+ // restores parameters as they were prior to the call
+ for (int i = 0; i < query.getParameter().size(); i++) {
+ final Variable param = query.getParameter().get(i);
+ getEvaluationEnvironment().replace(param.getName(), oldArgs[i]);
+ }
+ // [255379] restore self if need be
+ if (query.getParameter().size() > 0) {
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, oldArgs[oldArgs.length - 1]);
+ }
+ // Store result of the query invocation
+ if (queryResults.containsKey(query)) {
+ final Map<List<Object>, Object> results = queryResults.get(query);
+ results.put(arguments, result);
+ } else {
+ final Map<List<Object>, Object> results = new HashMap<List<Object>, Object>(2);
+ results.put(arguments, result);
+ queryResults.put(query, results);
+ }
+ return result;
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link Template}.
+ *
+ * @param template
+ * The Acceleo Template that is to be evaluated.
+ * @return Result of the template evaluation.
+ */
+ @SuppressWarnings("unchecked")
+ public String visitAcceleoTemplate(Template template) {
+ context.openNested();
+ /*
+ * Variables have been positionned by either the AcceleoEngine (first template) or this visitor (template
+ * invocation).
+ */
+ for (final OCLExpression nested : template.getBody()) {
+ visitExpression(nested);
+ }
+ return context.closeContext();
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link TemplateInvocation}.
+ *
+ * @param invocation
+ * The Acceleo template invocation that is to be evaluated.
+ * @return result of the invocation.
+ */
+ @SuppressWarnings("unchecked")
+ public Object visitAcceleoTemplateInvocation(TemplateInvocation invocation) {
+ // FIXME refactor this into multiple methods
+ context.openNested();
+ final Template template = invocation.getDefinition();
+ final List<Object> newArguments = new ArrayList<Object>();
+ final Object[] oldArgs;
+ // FIXME handle multiple invocations and "each"
+ final Template actualTemplate;
+ if (invocation.isSuper()) {
+ final Template containingTemplate = (Template)invocation.eContainer();
+ // Was the containing template called through another template invocation?
+ // If Yes, then this latter is the actual *super* we seek
+ // If No, then our super is the first template overriden
+ if (containingTemplate.eContainer() instanceof TemplateInvocation) {
+ actualTemplate = ((TemplateInvocation)containingTemplate.eContainer()).getDefinition();
+ } else {
+ actualTemplate = template.getOverrides().get(0);
+ }
+ // determine new values of argument variables and save the old
+ // [255379] size + 1 to keep old value of "self"
+ oldArgs = new Object[containingTemplate.getParameter().size() + 1];
+ for (int i = 0; i < actualTemplate.getParameter().size(); i++) {
+ final Variable param = actualTemplate.getParameter().get(i);
+ oldArgs[i] = getEvaluationEnvironment().getValueOf(param.getName());
+ final Object newArg = getEvaluationEnvironment().getValueOf(
+ containingTemplate.getParameter().get(i).getName());
+ getEvaluationEnvironment().replace(param.getName(), newArg);
+ newArguments.add(newArg);
+ // [255379] sets new value of "self" to match the very first arg of the invocation
+ if (i == 0) {
+ oldArgs[oldArgs.length - 1] = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, newArg);
+ }
+ }
+ } else {
+ // [255379] size + 1 to keep old value of "self"
+ oldArgs = new Object[invocation.getArgument().size() + 1];
+ // Determine values of the arguments
+ for (OCLExpression expression : invocation.getArgument()) {
+ final Object argValue = visitExpression(expression);
+ if (isUndefined(argValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEvaluationVisitor.UndefinedArgument", invocation //$NON-NLS-1$
+ .getStartPosition(), ((Module)EcoreUtil.getRootContainer(invocation))
+ .getName()));
+ exception.fillInStackTrace();
+ throw exception;
+ }
+ newArguments.add(argValue);
+ }
+ // retrieve all applicable candidates of the call
+ final List<Template> applicableCandidates = ((AcceleoEvaluationEnvironment)getEvaluationEnvironment())
+ .getAllCandidates((Module)EcoreUtil.getRootContainer(invocation), template, newArguments);
+ evaluateGuards(applicableCandidates, newArguments);
+ // We now know the actual template that's to be called
+ if (applicableCandidates.size() > 0) {
+ actualTemplate = ((AcceleoEvaluationEnvironment)getEvaluationEnvironment())
+ .getMostSpecificTemplate(applicableCandidates, newArguments);
+ // Set parameter values while retaining old ones
+ for (int i = 0; i < actualTemplate.getParameter().size(); i++) {
+ final Variable param = actualTemplate.getParameter().get(i);
+ oldArgs[i] = getEvaluationEnvironment().getValueOf(param.getName());
+ getEvaluationEnvironment().replace(param.getName(), newArguments.get(i));
+ // [255379] sets new value of "self" to match the very first arg of the invocation
+ if (i == 0) {
+ oldArgs[oldArgs.length - 1] = getEvaluationEnvironment().getValueOf(
+ SELF_VARIABLE_NAME);
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, newArguments.get(i));
+ }
+ }
+ } else {
+ // No template remains after guard evaluation. Create an empty template so no
+ // text will be generated from this call.
+ actualTemplate = MtlFactory.eINSTANCE.createTemplate();
+ }
+ }
+ if (invocation.getBefore() != null) {
+ visitExpression((OCLExpression)invocation.getBefore());
+ }
+ final Object source = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ if (source instanceof EObject) {
+ lastEObjectSelfValue = (EObject)source;
+ }
+ context.append(visitExpression((OCLExpression)actualTemplate).toString(), actualTemplate,
+ lastEObjectSelfValue);
+ if (invocation.getAfter() != null) {
+ visitExpression((OCLExpression)invocation.getAfter());
+ }
+
+ // restore parameters as they were prior to the call
+ for (int i = 0; i < actualTemplate.getParameter().size(); i++) {
+ final Variable param = actualTemplate.getParameter().get(i);
+ getEvaluationEnvironment().replace(param.getName(), oldArgs[i]);
+ }
+ // [255379] restore self if need be
+ if (actualTemplate.getParameter().size() > 0) {
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, oldArgs[oldArgs.length - 1]);
+ }
+ return context.closeContext();
+ }
+
+ /**
+ * This will evaluate guards of all <code>candidates</code> and filter out those whose guard is evaluated
+ * to <code>false</code>. Template with no specified guards aren't removed from the list.
+ * <p>
+ * <b>Note</b> that the parameter list will be modified by this call. Order will be preserved.
+ * </p>
+ *
+ * @param candidates
+ * List that is to be filtered.
+ * @param arguments
+ * Arguments of all templates. Those need to be set for guard evaluation.
+ */
+ @SuppressWarnings("unchecked")
+ private void evaluateGuards(List<Template> candidates, List<Object> arguments) {
+ AcceleoEvaluationException exception = null;
+ /*
+ * NOTE : we depend on the ordering offered by List types. Do not change Collection implementation to
+ * a non-ordered one.
+ */
+ for (final Template candidate : new ArrayList<Template>(candidates)) {
+ // Set parameter values while retaining old ones
+ // [255379] size + 1 to retain "self" value
+ final Object[] oldArgs = new Object[candidate.getParameter().size() + 1];
+ for (int i = 0; i < candidate.getParameter().size(); i++) {
+ final Variable param = candidate.getParameter().get(i);
+ oldArgs[i] = getEvaluationEnvironment().getValueOf(param.getName());
+ getEvaluationEnvironment().replace(param.getName(), arguments.get(i));
+ // [255379] sets new value of "self" to match the very first arg of the invocation
+ if (i == 0) {
+ oldArgs[oldArgs.length - 1] = getEvaluationEnvironment().getValueOf(SELF_VARIABLE_NAME);
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, arguments.get(i));
+ }
+ }
+ final Object guardValue;
+ if (candidate.getGuard() == null) {
+ guardValue = Boolean.TRUE;
+ } else {
+ guardValue = visitExpression((OCLExpression)candidate.getGuard());
+ }
+ if (isUndefined(guardValue)) {
+ exception = new AcceleoEvaluationException(AcceleoEngineMessages.getString(
+ UNDEFINED_GUARD_MESSAGE_KEY, candidate.getStartPosition(), ((Module)EcoreUtil
+ .getRootContainer(candidate)).getName()));
+ exception.fillInStackTrace();
+ candidates.remove(candidate);
+ continue;
+ }
+
+ if (!((Boolean)guardValue).booleanValue()) {
+ candidates.remove(candidate);
+ }
+
+ // restore parameters as they were prior to the call
+ for (int i = 0; i < candidate.getParameter().size(); i++) {
+ final Variable param = candidate.getParameter().get(i);
+ getEvaluationEnvironment().replace(param.getName(), oldArgs[i]);
+ }
+ // [255379] restore self if need be
+ if (candidate.getParameter().size() > 0) {
+ getEvaluationEnvironment().replace(SELF_VARIABLE_NAME, oldArgs[oldArgs.length - 1]);
+ }
+ }
+ if (candidates.size() == 0 && exception != null) {
+ throw exception;
+ }
+ }
+
+ /**
+ * Handles the evaluation of an Acceleo {@link InitSection}. This will simply add all the declared variables
+ * to the evaluation environment. Evaluation of all blocks should use this in order to save the variables
+ * state <u>before</u> evaluation and be able to restore it afterwards through {@link #restoreVariables()}
+ * .
+ *
+ * @param init
+ * The init section containing the variables to process.
+ */
+ @SuppressWarnings("unchecked")
+ private void handleAcceleoInitSection(InitSection init) {
+ final Map<String, Object> oldVariables = new HashMap<String, Object>(init.getVariable().size());
+ for (final Variable var : init.getVariable()) {
+ final String varName = var.getName();
+ final Object oldValue = getEvaluationEnvironment().getValueOf(varName);
+ oldVariables.put(varName, oldValue);
+ final Object newValue = visitExpression((OCLExpression)var.getInitExpression());
+ if (isUndefined(newValue)) {
+ final AcceleoEvaluationException exception = new AcceleoEvaluationException(
+ AcceleoEngineMessages
+ .getString(
+ "AcceleoEvaluationVisitor.UndefinedVariable", var.getStartPosition(), ((Module)EcoreUtil.getRootContainer(init)).getName())); //$NON-NLS-1$
+ exception.fillInStackTrace();
+ throw exception;
+ }
+ getEvaluationEnvironment().replace(varName, newValue);
+ }
+ context.saveVariableValues(oldVariables);
+ }
+
+ /**
+ * This will restore all variables in the environment according to the values saved within the evaluation
+ * context. This is called internally for all blocks that had an init section and shouldn't be called from
+ * anywhere else.
+ */
+ private void restoreVariables() {
+ final Map<String, Object> variables = context.getLastVariablesValues();
+ for (final Entry<String, Object> entry : variables.entrySet()) {
+ getEvaluationEnvironment().replace(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * This will check if the given reference should cause text generation. More specifically, it will return
+ * <code>false</code> for any reference that isn't one of the following :
+ * <table>
+ * <tr>
+ * <td>Metaclass</td>
+ * <td>Reference</td>
+ * </tr>
+ * <tr>
+ * <td>Block</td>
+ * <td>body</td>
+ * </tr>
+ * <tr>
+ * <td>ForBlock</td>
+ * <td>before</td>
+ * </tr>
+ * <tr>
+ * <td>ForBlock</td>
+ * <td>each</td>
+ * </tr>
+ * <tr>
+ * <td>ForBlock</td>
+ * <td>after</td>
+ * </tr>
+ * <tr>
+ * <td>TemplateInvocation</td>
+ * <td>before</td>
+ * </tr>
+ * <tr>
+ * <td>TemplateInvocation</td>
+ * <td>each</td>
+ * </tr>
+ * <tr>
+ * <td>TemplateInvocation</td>
+ * <td>after</td>
+ * </tr>
+ * </table>
+ *
+ * @param reference
+ * The reference for which we need to know if text is to be generated.
+ * @return <code>True</code> if we need to generate text for the given reference, <code>false</code>
+ * otherwise.
+ */
+ private boolean shouldGenerateText(EReference reference) {
+ // Note : sort this by order of frequency to allow shot-circuit evaluation
+ boolean generate = reference == MtlPackage.eINSTANCE.getBlock_Body();
+ generate = generate || reference == MtlPackage.eINSTANCE.getForBlock_Each();
+ generate = generate || reference == MtlPackage.eINSTANCE.getTemplateInvocation_Each();
+ generate = generate || reference == MtlPackage.eINSTANCE.getForBlock_Before();
+ generate = generate || reference == MtlPackage.eINSTANCE.getForBlock_After();
+ generate = generate || reference == MtlPackage.eINSTANCE.getTemplateInvocation_Before();
+ generate = generate || reference == MtlPackage.eINSTANCE.getTemplateInvocation_After();
+ return generate;
+ }
+
+ /**
+ * Returns <code>true</code> if the value is either <code>null</code> or equal to the OCL standard
+ * library's OCLInvalid object.
+ *
+ * @param value
+ * Value we need to test.
+ * @return <code>true</code> if the value is either <code>null</code> or equal to the OCL standard
+ * library's OCLInvalid object, <code>false</code> otherwise.
+ */
+ private boolean isUndefined(Object value) {
+ return value == null || value == oclInvalid;
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/utils/AcceleoDynamicTemplatesEclipseUtil.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/utils/AcceleoDynamicTemplatesEclipseUtil.java
new file mode 100644
index 0000000..c8c2806
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/internal/utils/AcceleoDynamicTemplatesEclipseUtil.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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.acceleo.engine.internal.utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.acceleo.common.IAcceleoConstants;
+import org.eclipse.acceleo.common.utils.ModelUtils;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.osgi.framework.Bundle;
+
+/**
+ * Eclipse-specific utilities for Acceleo dynamic templates. It will be initialized with all dynamic templates
+ * that could be parsed from the extension point if Eclipse is running and won't be used when outside of
+ * Eclipse.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public final class AcceleoDynamicTemplatesEclipseUtil {
+ /** Keeps track of the bundles extending this extension point. */
+ protected static final Map<Bundle, List<String>> EXTENDING_BUNDLES = new HashMap<Bundle, List<String>>();
+
+ /** This will contain the modules contained by this registry. */
+ private static final Set<Module> REGISTERED_MODULES = new LinkedHashSet<Module>();
+
+ /** ResourceSet that will be used to load modules. */
+ private static final ResourceSet RESOURCE_SET = new ResourceSetImpl();
+
+ /**
+ * Utility classes don't need a default constructor.
+ */
+ private AcceleoDynamicTemplatesEclipseUtil() {
+ // hides constructor
+ }
+
+ /**
+ * Adds a bundle in the extending bundles map.
+ *
+ * @param bundle
+ * The extending bundle.
+ * @param pathes
+ * Pathes where dynamic modules are located.
+ */
+ public static void addExtendingBundle(Bundle bundle, List<String> pathes) {
+ List<String> currentPathes = EXTENDING_BUNDLES.get(bundle);
+ if (currentPathes == null) {
+ currentPathes = new ArrayList<String>();
+ }
+ currentPathes.addAll(pathes);
+ EXTENDING_BUNDLES.put(bundle, currentPathes);
+ }
+
+ /**
+ * Returns all registered modules. The returned set is a copy of this instance's.
+ *
+ * @return A copy of the registered modules set.
+ */
+ public static Set<Module> getRegisteredModules() {
+ refreshModules();
+ return new LinkedHashSet<Module>(REGISTERED_MODULES);
+ }
+
+ /**
+ * Removes a bundle from the extending bundles map.
+ *
+ * @param bundle
+ * The bundle that is to be removed.
+ */
+ public static void removeExtendingBundle(Bundle bundle) {
+ EXTENDING_BUNDLES.remove(bundle);
+ }
+
+ /**
+ * This will register the module represented by <code>file</code>.
+ *
+ * @param file
+ * The module to register.
+ */
+ private static void addModule(File file) {
+ if (file.exists() && file.canRead()) {
+ try {
+ Resource res = ModelUtils.load(file, RESOURCE_SET).eResource();
+ for (EObject child : res.getContents()) {
+ if (child instanceof Module) {
+ REGISTERED_MODULES.add((Module)child);
+ }
+ }
+ } catch (IOException e) {
+ // FIXME propagate this
+ }
+ }
+ }
+
+ /**
+ * This will be called prior to all invocations of getRegisteredModules so as to be aware of new additions
+ * or removals of dynamic templates.
+ */
+ @SuppressWarnings("unchecked")
+ private static void refreshModules() {
+ // FIXME instead of unloading/reloading everything, load only the new and remove the no longer needed
+ for (Module module : REGISTERED_MODULES) {
+ final Resource resource = module.eResource();
+ if (resource != null) {
+ resource.unload();
+ RESOURCE_SET.getResources().remove(resource);
+ }
+ }
+ for (java.util.Map.Entry<Bundle, List<String>> entry : new LinkedHashSet<java.util.Map.Entry<Bundle, List<String>>>(
+ EXTENDING_BUNDLES.entrySet())) {
+ for (String path : entry.getValue()) {
+ String actualPath = path;
+ if (actualPath.charAt(0) != '/') {
+ actualPath = '/' + actualPath;
+ }
+ Enumeration<URL> emtlFiles = entry.getKey().findEntries(actualPath, "*." //$NON-NLS-1$
+ + IAcceleoConstants.EMTL_FILE_EXTENSION, true);
+ if (emtlFiles == null || !emtlFiles.hasMoreElements()) {
+ emtlFiles = entry.getKey().findEntries(actualPath.replace("/src", "/bin"), "*." //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ + IAcceleoConstants.EMTL_FILE_EXTENSION, true);
+ }
+ try {
+ while (emtlFiles.hasMoreElements()) {
+ final URL next = emtlFiles.nextElement();
+ final File moduleFile = new File(FileLocator.toFileURL(next).getFile());
+ addModule(moduleFile);
+ }
+ } catch (IOException e) {
+ // FIXME propagate this
+ }
+ }
+ }
+ }
+
+ /**
+ * Clears the registry from all registered bundles.
+ */
+ public static void clearRegistry() {
+ EXTENDING_BUNDLES.clear();
+ for (Resource resource : RESOURCE_SET.getResources()) {
+ resource.unload();
+ }
+ RESOURCE_SET.getResources().clear();
+ REGISTERED_MODULES.clear();
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoDynamicTemplatesRegistry.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoDynamicTemplatesRegistry.java
new file mode 100644
index 0000000..c4522ef
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoDynamicTemplatesRegistry.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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.acceleo.engine.service;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.acceleo.common.IAcceleoConstants;
+import org.eclipse.acceleo.common.utils.ModelUtils;
+import org.eclipse.acceleo.engine.internal.utils.AcceleoDynamicTemplatesEclipseUtil;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.emf.common.EMFPlugin;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+
+/**
+ * This will allow Acceleo to dynamically resolve templates overrides within the modules registered by this
+ * registry.
+ * <p>
+ * Take note that all modules will be loaded in the registry's resource set if loaded from the registry
+ * through {@link #addModulesFrom(File)}.
+ * </p>
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ * @since 0.8
+ */
+public final class AcceleoDynamicTemplatesRegistry {
+ /** Singleton instance of the registry. */
+ public static final AcceleoDynamicTemplatesRegistry INSTANCE = new AcceleoDynamicTemplatesRegistry();
+
+ /** This will contain the modules contained by this registry. */
+ private final Set<Module> registeredModules = new LinkedHashSet<Module>();
+
+ /** ResourceSet that will be used by the singleton instance to load modules. */
+ private final ResourceSet resourceSet = new ResourceSetImpl();
+
+ /**
+ * This class is a singleton. Access instance through {@link #INSTANCE}.
+ */
+ private AcceleoDynamicTemplatesRegistry() {
+ // hides default constructor
+ }
+
+ /**
+ * Adds a module to the registry.
+ *
+ * @param module
+ * Module that is to be registered for dynamic template resolution.
+ * @return <code>true</code> if the set didn't already contain <code>module</code>.
+ */
+ public boolean addModule(Module module) {
+ return registeredModules.add(module);
+ }
+
+ /**
+ * Adds a set of module to the registry.
+ *
+ * @param modules
+ * Modules that are to be registered for dynamic template resolution.
+ * @return <code>true</code> if the set didn't already contain one of the modules contained by
+ * <code>modules</code>.
+ */
+ public boolean addModules(Collection<Module> modules) {
+ return registeredModules.addAll(modules);
+ }
+
+ /**
+ * This will register all modules that can be retrieved from <code>file</code>.
+ * <p>
+ * That is, if <code>file</code> is a directory, this will iterate over all its direct and indirect
+ * children (except for &quot;CVS&quot; and &quot;.svn&quot; named sub-directories), then load and
+ * register all child representing a module. Otherwise, if <code>file</code> itself represents a module,
+ * it will be loaded and registered.
+ * </p>
+ * <p>
+ * If <code>file</code> is neither a module nor a directory, this will have no effect.
+ * </p>
+ *
+ * @param file
+ * The module to register or directory to iterate over for modules.
+ */
+ public void addModulesFrom(File file) {
+ if (file.exists() && file.canRead()) {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles(new FileFilter() {
+ public boolean accept(File pathname) {
+ if (!pathname.getAbsolutePath().matches("^.*(CVS|\\\\.svn)$")) { //$NON-NLS-1$
+ return true;
+ }
+ return false;
+ }
+ });
+ for (File child : children) {
+ addModulesFrom(child);
+ }
+ } else if (IAcceleoConstants.EMTL_FILE_EXTENSION.equals(file.getPath().substring(
+ file.getPath().lastIndexOf('.') + 1))) {
+ try {
+ Resource res = ModelUtils.load(file, resourceSet).eResource();
+ for (EObject child : res.getContents()) {
+ if (child instanceof Module) {
+ registeredModules.add((Module)child);
+ }
+ }
+ } catch (IOException e) {
+ // FIXME propagate this
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns all registered modules. The returned set is a copy of this instance's.
+ *
+ * @return A copy of the registered modules set.
+ */
+ public Set<Module> getRegisteredModules() {
+ final Set<Module> compound = new LinkedHashSet<Module>();
+ if (EMFPlugin.IS_ECLIPSE_RUNNING) {
+ compound.addAll(AcceleoDynamicTemplatesEclipseUtil.getRegisteredModules());
+ }
+ compound.addAll(registeredModules);
+ return compound;
+ }
+
+ /**
+ * Removes a module from the registry.
+ *
+ * @param module
+ * Module that is to be removed from dynamic template resolution.
+ * @return <code>true</code> if the set contained <code>module</code>.
+ */
+ public boolean removeModule(Module module) {
+ return registeredModules.remove(module);
+ }
+
+ /**
+ * Removes a set of modules from the registry.
+ *
+ * @param modules
+ * Modules that are to be removed from dynamic template resolution.
+ * @return <code>true</code> if the set has been changed.
+ */
+ public boolean removeModules(Collection<Module> modules) {
+ return registeredModules.remove(modules);
+ }
+
+ /**
+ * This can be used to clear the registry's resource set from manually loaded Modules. Take note that
+ * modules loaded from extension points will not be cleared by this.
+ */
+ public void clearRegistryResourceSet() {
+ for (Resource res : resourceSet.getResources()) {
+ res.unload();
+ }
+ resourceSet.getResources().clear();
+ }
+}
diff --git a/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoService.java b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoService.java
new file mode 100644
index 0000000..ca629ae
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.engine/src/org/eclipse/acceleo/engine/service/AcceleoService.java
@@ -0,0 +1,489 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.acceleo.engine.service;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.acceleo.engine.AcceleoEngineMessages;
+import org.eclipse.acceleo.engine.AcceleoEvaluationException;
+import org.eclipse.acceleo.engine.event.AcceleoTextGenerationListener;
+import org.eclipse.acceleo.engine.generation.AcceleoGenericEngine;
+import org.eclipse.acceleo.engine.generation.IAcceleoEngine;
+import org.eclipse.acceleo.model.mtl.Module;
+import org.eclipse.acceleo.model.mtl.ModuleElement;
+import org.eclipse.acceleo.model.mtl.Template;
+import org.eclipse.acceleo.model.mtl.VisibilityKind;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * This class provides utility methods to launch the generation of an Acceleo template.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public final class AcceleoService {
+ /** The engine we'll use for all generations through this service. */
+ private static final IAcceleoEngine GENERATION_ENGINE = new AcceleoGenericEngine();
+
+ /** This message will be set for all NPE thrown because of null arguments for this utility's methods. */
+ private static final String TEMPLATE_CALL_NPE = AcceleoEngineMessages.getString("AcceleoService.NullArguments"); //$NON-NLS-1$
+
+ /**
+ * Utility classes don't need to (and shouldn't) be instantiated.
+ */
+ private AcceleoService() {
+ // prevents instantiation
+ }
+
+ /**
+ * Registers a listener to be notified for any text generation that will take place in this engine
+ * evaluation process. This will have to be removed manually through
+ * {@link #removeListener(AcceleoTextGenerationListener)}.
+ *
+ * @param listener
+ * The new listener that is to be registered for notification.
+ */
+ public static void addListener(AcceleoTextGenerationListener listener) {
+ GENERATION_ENGINE.addListener(listener);
+ }
+
+ /**
+ * This can be used to launch the generation of multiple Acceleo templates given their names and their
+ * containing modules.
+ * <p>
+ * Keep in mind that this can only be used with single-argument templates. Any attempt to call to a
+ * template with more than one argument through this method will throw {@link AcceleoEvaluationException}s.
+ * </p>
+ * <p>
+ * The input model will be iterated over for objects matching the templates' parameter types.
+ * </p>
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for each class of the input model.
+ * </p>
+ *
+ * @param templates
+ * This map will be used to locate templates of the given names in the associated module.
+ * @param model
+ * Input model for this generation.
+ * @param generationRoot
+ * This will be used as the root for the generated files. Cannot be <code>null</code> except if
+ * <code>preview</code> is <code>true</code> in which case no files will be generated.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ public static Map<String, StringWriter> doGenerate(Map<Module, Set<String>> templates, EObject model,
+ File generationRoot, boolean preview) {
+ if (templates == null || model == null || (!preview && generationRoot == null)) {
+ throw new NullPointerException(TEMPLATE_CALL_NPE);
+ }
+ Map<EClassifier, Set<Template>> templateTypes = new HashMap<EClassifier, Set<Template>>();
+ for (Map.Entry<Module, Set<String>> entry : templates.entrySet()) {
+ for (String templateName : entry.getValue()) {
+ Template template = findTemplate(entry.getKey(), templateName, 1);
+ EClassifier templateType = template.getParameter().get(0).getType();
+ if (templateTypes.containsKey(templateType)) {
+ templateTypes.get(templateType).add(template);
+ } else {
+ Set<Template> temp = new HashSet<Template>();
+ temp.add(template);
+ templateTypes.put(templateType, temp);
+ }
+ }
+ }
+
+ final Map<String, StringWriter> previewResult = new HashMap<String, StringWriter>();
+
+ // Calls all templates with each of their potential arguments
+ final List<Object> arguments = new ArrayList<Object>();
+ // The input model itself is a potential argument
+ arguments.add(model);
+ for (Map.Entry<EClassifier, Set<Template>> entry : templateTypes.entrySet()) {
+ if (entry.getKey().isInstance(model)) {
+ for (Template template : entry.getValue()) {
+ previewResult.putAll(doGenerateTemplate(template, arguments, generationRoot, preview));
+ }
+ }
+ }
+ final TreeIterator<EObject> targetElements = model.eAllContents();
+ while (targetElements.hasNext()) {
+ final EObject potentialTarget = targetElements.next();
+ for (Map.Entry<EClassifier, Set<Template>> entry : templateTypes.entrySet()) {
+ if (entry.getKey().isInstance(potentialTarget)) {
+ arguments.clear();
+ arguments.add(potentialTarget);
+ for (Template template : entry.getValue()) {
+ previewResult
+ .putAll(doGenerateTemplate(template, arguments, generationRoot, preview));
+ }
+ }
+ }
+ }
+
+ return previewResult;
+ }
+
+ /**
+ * Launches the generation of an Acceleo template given its name and containing module.
+ * <p>
+ * This is a convenience method that can only be used with single argument templates. The input model will
+ * be iterated over for objects matching the template's parameter type.
+ * </p>
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for each class of the input model.
+ * </p>
+ *
+ * @param module
+ * The module in which we seek a template <tt>templateName</tt>.
+ * @param templateName
+ * Name of the template that is to be generated.
+ * @param model
+ * Input model for this Acceleo template.
+ * @param generationRoot
+ * This will be used as the root for the generated files. This can be <code>null</code>, in
+ * which case the user home directory will be used as root.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ public static Map<String, StringWriter> doGenerate(Module module, String templateName, EObject model,
+ File generationRoot, boolean preview) {
+ return doGenerate(findTemplate(module, templateName, 1), model, generationRoot, preview);
+ }
+
+ /**
+ * Launches the generation of an Acceleo template given its name and containing module.
+ * <p>
+ * This is a convenience method that can be used with multiple argument templates. The input model will be
+ * iterated over for objects matching the template's <b>first</b> parameter type. The template will then
+ * be called with these objects as first arguments, and the given list of <code>arguments</code> for the
+ * remaining template parameters.
+ * </p>
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for each class of the input model.
+ * </p>
+ *
+ * @param module
+ * The module in which we seek a template <tt>templateName</tt>.
+ * @param templateName
+ * Name of the template that is to be generated.
+ * @param model
+ * Input model for this Acceleo template.
+ * @param arguments
+ * Arguments of the template call, excluding the very first one (<code>model</code> object).
+ * @param generationRoot
+ * This will be used as the root for the generated files. This can be <code>null</code>, in
+ * which case the user home directory will be used as root.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ public static Map<String, StringWriter> doGenerate(Module module, String templateName, EObject model,
+ List<? extends Object> arguments, File generationRoot, boolean preview) {
+ if (model == null || arguments == null || (!preview && generationRoot == null)) {
+ throw new NullPointerException(TEMPLATE_CALL_NPE);
+ }
+ final Template template = findTemplate(module, templateName, arguments.size() + 1);
+ if (template.getVisibility() != VisibilityKind.PUBLIC) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEngine.IllegalTemplateInvocation")); //$NON-NLS-1$
+ }
+
+ final Map<String, StringWriter> previewResult = new HashMap<String, StringWriter>();
+
+ // Calls the template with each potential arguments
+ final EClassifier argumentType = template.getParameter().get(0).getType();
+ // The input model itself is a potential argument
+ if (argumentType.isInstance(model)) {
+ final List<Object> actualArguments = new ArrayList<Object>();
+ actualArguments.add(model);
+ actualArguments.addAll(arguments);
+ previewResult.putAll(doGenerateTemplate(template, actualArguments, generationRoot, preview));
+ }
+ final TreeIterator<EObject> targetElements = model.eAllContents();
+ while (targetElements.hasNext()) {
+ final EObject potentialTarget = targetElements.next();
+ if (argumentType.isInstance(potentialTarget)) {
+ final List<Object> actualArguments = new ArrayList<Object>();
+ actualArguments.add(potentialTarget);
+ actualArguments.addAll(arguments);
+ previewResult.putAll(doGenerateTemplate(template, actualArguments, generationRoot, preview));
+ }
+ }
+
+ return previewResult;
+ }
+
+ /**
+ * Launches the generation of a single-argument Acceleo template for all matching EObjects in the given model.
+ * <p>
+ * This is a convenience method that can only be used with single argument templates. Any attempt at
+ * calling other templates through this method will throw {@link AcceleoEvaluationException}s. The input model
+ * will be iterated over for objects matching the template's parameter type.
+ * </p>
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for each class of the input model.
+ * </p>
+ *
+ * @param template
+ * The template that is to be generated
+ * @param model
+ * Input model for this Acceleo template.
+ * @param generationRoot
+ * This will be used as the root for the generated files. This can be <code>null</code>, in
+ * which case the user home directory will be used as root.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ public static Map<String, StringWriter> doGenerate(Template template, EObject model, File generationRoot,
+ boolean preview) {
+ if (template == null || model == null || (!preview && generationRoot == null)) {
+ throw new NullPointerException(TEMPLATE_CALL_NPE);
+ }
+ if (template.getVisibility() != VisibilityKind.PUBLIC) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages
+ .getString("AcceleoEngine.IllegalTemplateInvocation")); //$NON-NLS-1$
+ }
+ if (template.getParameter().size() != 1) {
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoEngine.VoidArguments")); //$NON-NLS-1$
+ }
+
+ final Map<String, StringWriter> previewResult = new HashMap<String, StringWriter>();
+
+ // Calls the template with each potential arguments
+ final EClassifier argumentType = template.getParameter().get(0).getType();
+ final List<Object> arguments = new ArrayList<Object>();
+ // The input model itself is a potential argument
+ if (argumentType.isInstance(model)) {
+ arguments.add(model);
+ previewResult.putAll(doGenerateTemplate(template, arguments, generationRoot, preview));
+ }
+ final TreeIterator<EObject> targetElements = model.eAllContents();
+ while (targetElements.hasNext()) {
+ final EObject potentialTarget = targetElements.next();
+ if (argumentType.isInstance(potentialTarget)) {
+ arguments.clear();
+ arguments.add(potentialTarget);
+ previewResult.putAll(doGenerateTemplate(template, arguments, generationRoot, preview));
+ }
+ }
+
+ return previewResult;
+ }
+
+ /**
+ * Launches the generation of an Acceleo template with the given arguments.
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for each class of the input model.
+ * </p>
+ *
+ * @param module
+ * The module in which we seek a template <tt>templateName</tt>.
+ * @param templateName
+ * Name of the template that is to be generated.
+ * @param arguments
+ * Arguments that must be passed on to the template for evaluation.
+ * @param generationRoot
+ * This will be used as the root for the generated files. This can be <code>null</code>, in
+ * which case the user home directory will be used as root.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ public static Map<String, StringWriter> doGenerateTemplate(Module module, String templateName,
+ List<? extends Object> arguments, File generationRoot, boolean preview) {
+ return doGenerateTemplate(findTemplate(module, templateName, arguments), arguments, generationRoot,
+ preview);
+ }
+
+ /**
+ * Launches the generation of an Acceleo template with the given arguments.
+ * <p>
+ * <tt>generationRoot</tt> will be used as the root of all generated files. For example, a template such
+ * as
+ *
+ * <pre>
+ * [template generate(c:EClass)]
+ * [file(log.log, true)]processing class [c.name/][/file]
+ * [/template]
+ * </pre>
+ *
+ * evaluated with <tt>file:\\c:\</tt> as <tt>generationRoot</tt> would create the file <tt>c:\log.log</tt>
+ * and generate a line &quot;processing class &lt;className&gt;&quot; for each class of the input model.
+ * </p>
+ *
+ * @param template
+ * The template that is to be generated
+ * @param arguments
+ * Arguments that must be passed on to the template for evaluation.
+ * @param generationRoot
+ * This will be used as the root for the generated files. This can be <code>null</code>, in
+ * which case the user home directory will be used as root.
+ * @param preview
+ * If <code>true</code>, no files will be generated and a Map mapping file pathes to their
+ * generated content will be returned.
+ * @return if <code>preview</code> is set to <code>true</code>, no files will be generated. Instead, a Map
+ * mapping all file pathes to the potential content will be returned. This returned map will be
+ * empty otherwise.
+ */
+ public static Map<String, StringWriter> doGenerateTemplate(Template template,
+ List<? extends Object> arguments, File generationRoot, boolean preview) {
+ return GENERATION_ENGINE.evaluate(template, arguments, generationRoot, preview);
+ }
+
+ /**
+ * Removes a listener from the notification loops.
+ *
+ * @param listener
+ * The listener that is to be removed from this engine's notification loops.
+ */
+ public static void removeListener(AcceleoTextGenerationListener listener) {
+ GENERATION_ENGINE.removeListener(listener);
+ }
+
+ /**
+ * This will iterate through the module's elements to find public templates named <tt>templateName</tt>
+ * with the given count of arguments and return the first found.
+ *
+ * @param module
+ * The module in which we seek a template <tt>templateName</tt>.
+ * @param templateName
+ * Name of the sought template.
+ * @param argumentCount
+ * Number of arguments of the sought template.
+ * @return The first public template of this name contained by <tt>module</tt>. Will fail in
+ * {@link AcceleoEvaluationException} if none can be found.
+ */
+ private static Template findTemplate(Module module, String templateName, int argumentCount) {
+ for (ModuleElement element : module.getOwnedModuleElement()) {
+ if (element instanceof Template) {
+ Template template = (Template)element;
+ if (template.getVisibility() == VisibilityKind.PUBLIC
+ && templateName.equals(template.getName())
+ && template.getParameter().size() == argumentCount) {
+ return template;
+ }
+ }
+ }
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoService.UndefinedTemplate", //$NON-NLS-1$
+ templateName, module.getName()));
+ }
+
+ /**
+ * This will iterate through the module's elements to find public templates which argument types
+ * correspond to the given list of argument values.
+ *
+ * @param module
+ * The module in which we seek the template.
+ * @param templateName
+ * Name of the sought template.
+ * @param arguments
+ * Values of the argument we wish to pass on to the template.
+ * @return The first public template of this name with matching arguments contained by <tt>module</tt>.
+ * Will fail in {@link AcceleoEvaluationException} if none can be found.
+ */
+ private static Template findTemplate(Module module, String templateName, List<? extends Object> arguments) {
+ for (ModuleElement element : module.getOwnedModuleElement()) {
+ if (element instanceof Template) {
+ Template template = (Template)element;
+ if (template.getVisibility() == VisibilityKind.PUBLIC
+ && templateName.equals(template.getName())
+ && template.getParameter().size() == arguments.size()) {
+ boolean parameterMatch = true;
+ for (int i = 0; i < template.getParameter().size(); i++) {
+ if (!template.getParameter().get(i).getType().isInstance(arguments.get(i))) {
+ parameterMatch = false;
+ }
+ }
+ if (parameterMatch) {
+ return template;
+ }
+ }
+ }
+ }
+ throw new AcceleoEvaluationException(AcceleoEngineMessages.getString("AcceleoService.UndefinedTemplate", //$NON-NLS-1$
+ templateName, module.getName()));
+ }
+}