diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d42f5c1f..f1ffe8d0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- [#86](https://github.com/gemoc/ale-lang/issues/86) The XMI model and the ALE entry point to execute can be set from the *Launch Configuration Tab*
- [#92](https://github.com/gemoc/ale-lang/issues/92) The editor autocompletes attributes and methods of `self`
- [#94](https://github.com/gemoc/ale-lang/issues/94) The editor automatically switches to dark colors when Eclipse IDE is in dark theme
+- [#98](https://github.com/gemoc/ale-lang/issues/98) The _New ALE Project_ wizard can be used to create ALE projects
### Changed
- [#93](https://github.com/gemoc/ale-lang/issues/93) More tokens are available to tailor editor's syntax coloring
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/META-INF/MANIFEST.MF
index f4002f316..0eaaa5d45 100644
--- a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/META-INF/MANIFEST.MF
@@ -6,7 +6,9 @@ Bundle-Version: 1.1.0.qualifier
Bundle-ActivationPolicy: lazy
Bundle-Activator: org.eclipse.emf.ecoretools.ale.ide.ui.Activator
Export-Package: org.eclipse.emf.ecoretools.ale.ide.ui.launchconfig,
- org.eclipse.emf.ecoretools.ale.ide.ui.services
+ org.eclipse.emf.ecoretools.ale.ide.ui.project;x-friends:="org.eclipse.emf.ecoretools.ale.ide.ui.tests",
+ org.eclipse.emf.ecoretools.ale.ide.ui.services,
+ org.eclipse.emf.ecoretools.ale.ide.ui.wizards
Require-Bundle: org.eclipse.emf.ecoretools.ale.ide,
org.eclipse.ui.workbench,
org.eclipse.ui,
@@ -21,7 +23,11 @@ Require-Bundle: org.eclipse.emf.ecoretools.ale.ide,
org.eclipse.sirius.common,
org.eclipse.sirius.common.acceleo.aql,
org.eclipse.emf.ecoretools.ale.xtext,
- org.eclipse.emf.ecoretools.design
+ org.eclipse.emf.ecoretools.design,
+ org.eclipse.jdt.core;resolution:=optional,
+ org.eclipse.jdt.launching;resolution:=optional,
+ org.eclipse.sirius.ext.base,
+ org.eclipse.sirius.ui
Bundle-Vendor: Inria/Obeo
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Automatic-Module-Name: org.eclipse.emf.ecoretools.ale.ide.ui
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/plugin.xml b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/plugin.xml
index dba5753c9..41f5db7c6 100644
--- a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/plugin.xml
@@ -102,4 +102,19 @@
id="org.eclipse.emf.ecoretools.ale.ide.ui.launcher.image">
+
+
+
+
+
+
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/launchconfig/UiUtils.java b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/launchconfig/UiUtils.java
index cd02270ca..9d51d1b06 100644
--- a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/launchconfig/UiUtils.java
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/launchconfig/UiUtils.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2019 Inria and Obeo.
+ * Copyright (c) 2019-2020 Inria and 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
@@ -18,7 +18,7 @@
/**
* Utility methods for dealing with UI.
*/
-final class UiUtils {
+public final class UiUtils {
private UiUtils() {
// utility class should not be instantiated
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/project/WorkspaceAleProject.java b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/project/WorkspaceAleProject.java
new file mode 100644
index 000000000..766ca653c
--- /dev/null
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/project/WorkspaceAleProject.java
@@ -0,0 +1,455 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Inria and 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:
+ * Inria - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.ecoretools.ale.ide.ui.project;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+import static java.util.Objects.requireNonNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EcoreFactory;
+import org.eclipse.emf.ecore.EcorePackage;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecoretools.ale.core.parser.Dsl;
+import org.eclipse.emf.ecoretools.ale.ide.project.AleProject;
+import org.eclipse.emf.ecoretools.ale.ide.ui.Activator;
+import org.eclipse.emf.transaction.RecordingCommand;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.JavaRuntime;
+import org.eclipse.sirius.business.api.componentization.ViewpointRegistry;
+import org.eclipse.sirius.business.api.dialect.DialectManager;
+import org.eclipse.sirius.business.api.session.Session;
+import org.eclipse.sirius.business.api.session.SessionManager;
+import org.eclipse.sirius.business.internal.session.SessionTransientAttachment;
+import org.eclipse.sirius.business.internal.session.danalysis.SaveSessionJob;
+import org.eclipse.sirius.ui.business.api.viewpoint.ViewpointSelectionCallback;
+import org.eclipse.sirius.ui.business.internal.commands.ChangeViewpointSelectionCommand;
+import org.eclipse.sirius.viewpoint.DView;
+import org.eclipse.sirius.viewpoint.description.RepresentationDescription;
+import org.eclipse.sirius.viewpoint.description.Viewpoint;
+import org.eclipse.xtext.ui.XtextProjectHelper;
+
+/**
+ * An {@link AleProject ALE project} located in the workspace.
+ */
+public class WorkspaceAleProject implements AleProject {
+ /**
+ * The workspace containing the project.
+ */
+ private final IWorkspace workspace;
+ /**
+ * The description of the project.
+ */
+ private final Description description;
+
+ /**
+ * Instantiates a new ALE project located in the workspace.
+ *
+ * @param workspace
+ * The workspace containing the project.
+ */
+ public WorkspaceAleProject(IWorkspace workspace, Description description) {
+ this.workspace = requireNonNull(workspace, "workspace");
+ this.description = requireNonNull(description, "description");
+ }
+
+ @Override
+ public IProject create(String name, IPath path, IProgressMonitor monitor) throws CoreException {
+ IWorkspaceRunnable createProject = projectMonitor -> {
+ SubMonitor subMonitor = SubMonitor.convert(projectMonitor, "Creating ALE project", 60);
+ try {
+ IProject project = createProject(name, path, subMonitor.split(1));
+ EPackage epackage = getOrCreateEcoreModel(project, subMonitor.split(1));
+ IPath aleFilePath = createAleSourceFile(project, epackage, subMonitor.split(1));
+
+ createDslFile(project, asList(URI.createPlatformResourceURI(epackage.eResource().getURI().toString(), true).toString()), asList(URI.createPlatformResourceURI(aleFilePath.toPortableString(), true).toString()), subMonitor.split(1));
+ createRepresentation(project, epackage, subMonitor.split(1));
+ addJavaNature(project, subMonitor.split(1));
+
+ project.refreshLocal(IResource.DEPTH_INFINITE, subMonitor.split(1));
+ }
+ catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
+ }
+ finally {
+ monitor.done();
+ }
+ };
+ workspace.run(createProject, monitor);
+ return workspace.getRoot().getProject(name);
+ }
+
+ /**
+ * Creates a new IProject and persists it on disk.
+ */
+ private IProject createProject(String name, IPath path, IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, "Creating the project...", 3);
+ IProject project = workspace.getRoot().getProject(name);
+
+ if (project.exists()) {
+ throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "project " + name + " already exists in the workspace"));
+ }
+ IProjectDescription desc = workspace.newProjectDescription(name);
+ desc.setLocation(path);
+ desc.setNatureIds(new String[] { XtextProjectHelper.NATURE_ID });
+
+ subMonitor.subTask("Creating the project " + name + "...");
+ project.create(desc, subMonitor.split(1));
+
+ subMonitor.subTask("Opening the project " + name + "...");
+ project.open(subMonitor.split(1));
+
+ subMonitor.subTask("Creating the model folder...");
+ project.getFolder("model").create(false, true, subMonitor.split(1));
+
+ return project;
+ }
+
+ private EPackage getOrCreateEcoreModel(IProject project, IProgressMonitor monitor) throws IOException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, "Creating the Ecore model...", 1);
+
+ if (!description.useAnExistingEcoreModel) {
+ return createEcoreModel(project, description.ecorePackageName, subMonitor.split(1));
+ }
+ else {
+ return getEcoreModel(description.ecoreModelFilePath);
+ }
+ }
+
+ private static EPackage createEcoreModel(IProject project, String packageName, IProgressMonitor monitor) throws IOException {
+ EPackage pkg = EcoreFactory.eINSTANCE.createEPackage();
+ pkg.setName(packageName);
+ pkg.setNsPrefix(packageName);
+ pkg.setNsURI("http://" + packageName);
+
+ IFile ecoreFile = project.getFolder("model").getFile(project.getName() + ".ecore");
+ ResourceSet resources = new ResourceSetImpl();
+ Resource resource = resources.createResource(URI.createFileURI(ecoreFile.getFullPath().toOSString()));
+
+ resource.getContents().add(pkg);
+ resource.save(emptyMap());
+
+ return pkg;
+ }
+
+ private IPath createAleSourceFile(IProject project, EPackage epackage, IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, "Creating ALE source file...", 1);
+
+ String initialContent = "behavior " + epackage.getName() + ";";
+ InputStream readableInitialContent = new ByteArrayInputStream(initialContent.getBytes());
+
+ IFile src = project.getFolder("model").getFile(project.getName() + ".ale");
+ src.create(readableInitialContent, false, subMonitor.split(1));
+
+ return src.getFullPath();
+ }
+
+ private static EPackage getEcoreModel(IPath path) {
+ ResourceSet resources = new ResourceSetImpl();
+ Resource resource = resources.getResource(URI.createFileURI(path.toOSString()), true);
+ return (EPackage) resource.getContents().get(0);
+ }
+
+ private static void createDslFile(IProject project, List ecoreModels, List aleSourceFiles, IProgressMonitor monitor) {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, "Creating .dsl file...", 1);
+
+ Dsl dsl = new Dsl(ecoreModels, aleSourceFiles);
+ dsl.setSourceFile(project.getFolder("model").getFile(project.getName() + ".dsl").getLocation().toOSString());
+ dsl.save();
+
+ subMonitor.worked(1);
+ }
+
+ private void createRepresentation(IProject project, EObject model, IProgressMonitor monitor) throws CoreException {
+ if (! description.createRepresentation) {
+ return;
+ }
+ SubMonitor subMonitor = SubMonitor.convert(monitor, "Creating Sirius representation...", 4);
+
+ // Create a new Sirius session to handle the representation
+
+ URI sessionResourceURI = URI.createPlatformResourceURI(project.getFolder("model").getFile(project.getName() + ".aird").getFullPath().toOSString(), true);
+ Session session = SessionManager.INSTANCE.getSession(sessionResourceURI, new NullProgressMonitor());
+ session.open(new NullProgressMonitor());
+
+ // Allow to create a representation for the model thereafter
+
+ session.getTransactionalEditingDomain().getCommandStack()
+ .execute(new RecordingCommand(session.getTransactionalEditingDomain()) {
+ @Override
+ protected void doExecute() {
+ session.addSemanticResource(URI.createURI(EcorePackage.eNS_URI), subMonitor);
+
+ // Actually Sirius seems to automatically add model's EResource, so the following is useless
+// session.addSemanticResource(URI.createPlatformResourceURI(model.eResource().getURI().toString(), true), subMonitor.split(1));
+ }
+ });
+
+ // Add the 'Design' viewpoint to the representation
+
+ Optional maybeDesignViewpoint = findDesignViewpoint(ViewpointRegistry.getInstance());
+ if (! maybeDesignViewpoint.isPresent()) {
+ Activator.error("Cannot create Sirius representation in project " + project.getName() + ": 'Design' viewpoint not found", null);
+ return;
+ }
+ Viewpoint designViewpoint = maybeDesignViewpoint.get();
+ ChangeViewpointSelectionCommand cc = new ChangeViewpointSelectionCommand(
+ session,
+ new ViewpointSelectionCallback(),
+ new HashSet<>(asList(designViewpoint)),
+ emptySet(),
+ subMonitor.split(1)
+ );
+ session.getTransactionalEditingDomain().getCommandStack().execute(cc);
+ session.getTransactionalEditingDomain().getCommandStack()
+ .execute(new RecordingCommand(session.getTransactionalEditingDomain()) {
+ @Override
+ protected void doExecute() {
+ session.createView(maybeDesignViewpoint.get(), asList(model), true, subMonitor.split(1));
+ }
+ });
+
+ // Okay, that's tricky.
+ //
+ // For some reason there are several instances of the 'Design' viewpoints.
+ // However, when we use the one returned by the ViewpointRegistry Sirius doesn't create any representation
+ // for our model because it is doesn't match the instance it uses internally (the `equals` method should
+ // have been overridden).
+ //
+ // As a result, we retrieve the correct instance of the viewpoint below so that Sirius actually
+ // creates the diagram representation.
+ //
+ // FIXME here: I found out by spending lot of time in the debugger, this solution is awful and will likely
+ // not work with other versions of Sirius (hopefully it won't even be needed). I may missed something
+ // so if a better solution exist please improve!
+
+ Collection selectedViews = session.getSelectedViews();
+ if (! selectedViews.isEmpty()) {
+ DView view = selectedViews.iterator().next();
+ designViewpoint = view.getViewpoint();
+ }
+
+ // Finally create a representation of our Ecore model from the 'Entities' template
+
+ Optional representationDescription =
+ DialectManager.INSTANCE.getAvailableRepresentationDescriptions(asList(designViewpoint), model)
+ .stream()
+ .filter(desc -> desc.getName().equals("Entities"))
+ .findAny();
+
+ if (! representationDescription.isPresent()) {
+ Activator.error("Cannot create Sirius representation in project " + project.getName() + ": 'Entities' representation not found", null);
+ return;
+ }
+ // Otherwise Sirius is not able to find the session from the model
+ model.eAdapters().add(new SessionTransientAttachment(session));
+
+ session.getTransactionalEditingDomain().getCommandStack()
+ .execute(new RecordingCommand(session.getTransactionalEditingDomain()) {
+ @Override
+ protected void doExecute() {
+ DialectManager.INSTANCE.createRepresentation(
+ project.getName() + " class diagram",
+ model,
+ representationDescription.get(),
+ session,
+ subMonitor.split(1)
+ );
+ }
+ });
+
+ // Another trick to prevent issues when creating a representation for existing Ecore models
+ //
+ // Issue description:
+ // -----------------
+ //
+ // When one attempts to initialize the representation with the EPackage content Sirius throws an error.
+ //
+ // Steps to reproduce:
+ // ------------------
+ //
+ // 1. Create a new ALE project relying on an existing Ecore model
+ // 2. Open the .aird representation file
+ // 3. Open the model's representation ('? class diagram')
+ // 4. Double-click on 'Double-click to initialize ...'
+ //
+ // Explanation:
+ // -----------
+ //
+ // According to [1], when Sirius adds the external Ecore model as a semantic resource of the session it
+ // actually adds a _copy_ of the resource. It then mistakenly believes that Ecore classes are not part
+ // of the session's semantic resources and throws a exception.
+ //
+ // [1] https://www.eclipse.org/forums/index.php?t=msg&th=1085757&goto=1760173msg_1760173
+ //
+ // Workaround:
+ // ----------
+ //
+ // It looks like closing the session clears some internal state and make everything work as expected.
+ //
+ // BUT.
+ //
+ // We cannot close the session whenever we want. When we modify the session the changes are not synchronously
+ // persisted in the .aird file. Instead, a SaveSessionJob is schedule in background (when? I don't really know)
+ // and we have to wait for it to end before closing the session.
+ //
+ // BUT.
+ //
+ // For some reason:
+ // - calling Job.getJobManager().join(SaveSessionJob.FAMILY) while a SaveSessionJob is actually running
+ // ends up in an infinite loop,
+ // - using an infinite loop (while or for) and checking periodically whether the job has ended does not
+ // work either (the job runs forever).
+ //
+ // In the end, the only reliable solution I've found is to schedule another Job responsible of closing
+ // the session and that reschedules itself until the SaveSessionJob is done.
+
+ new Job("Close Sirius session of project " + project.getName()) {
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ boolean sessionIsSaved = Job.getJobManager().find(SaveSessionJob.FAMILY).length == 0;
+
+ if (! sessionIsSaved) {
+ // The session has not been saved yet, we cannot close
+ // Reschedule the job so we can try later
+ schedule(5);
+ }
+ else {
+ session.close(new NullProgressMonitor());
+ }
+ return Status.OK_STATUS;
+ }
+
+ }.schedule();
+ }
+
+ private static Optional findDesignViewpoint(ViewpointRegistry registry) {
+ return registry.getViewpoints().stream()
+ .filter(viewpoint -> registry.isFromPlugin(viewpoint))
+ .filter(viewpoint -> "Design".equals(viewpoint.getName()))
+ .findAny();
+ }
+
+ private void addJavaNature(IProject project, IProgressMonitor monitor) throws CoreException {
+ if (! description.activateJava) {
+ return;
+ }
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 6);
+ try {
+ subMonitor.setTaskName("Adding Java nature...");
+
+ IProjectDescription desc = project.getDescription();
+ String[] prevNatures = desc.getNatureIds();
+ String[] newNatures = new String[prevNatures.length + 1];
+ System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
+ newNatures[prevNatures.length] = JavaCore.NATURE_ID;
+ desc.setNatureIds(newNatures);
+ project.setDescription(desc, subMonitor.split(1));
+
+ subMonitor.setTaskName("Configuring classpath...");
+
+ IJavaProject javaProject = JavaCore.create(project);
+
+ // Set bin/ as output location
+
+ IFolder binFolder = project.getFolder("bin");
+ binFolder.create(false, true, subMonitor.split(1));
+ javaProject.setOutputLocation(binFolder.getFullPath(), subMonitor.split(1));
+
+ // Add JRE to classpath
+
+ List entries = new ArrayList<>();
+ entries.add(JavaRuntime.getDefaultJREContainerEntry());
+ javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries.size()]), subMonitor.split(1));
+
+ // Set src/ as source folder
+
+ IFolder sourceFolder = project.getFolder("src");
+ sourceFolder.create(false, true, subMonitor.split(1));
+
+ IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(sourceFolder);
+ IClasspathEntry[] oldEntries = javaProject.getRawClasspath();
+ IClasspathEntry[] newEntries = new IClasspathEntry[oldEntries.length + 1];
+ System.arraycopy(oldEntries, 0, newEntries, 0, oldEntries.length);
+ newEntries[oldEntries.length] = JavaCore.newSourceEntry(root.getPath());
+ javaProject.setRawClasspath(newEntries, subMonitor.split(1));
+ }
+ catch (NoClassDefFoundError e) {
+ Activator.error("Unable to set Java nature to project '" + project.getName() + "': org.eclipse.jdt.core or org.eclipse.jdt.launching is not available", e);
+ }
+ }
+
+ /**
+ * The properties of an ALE project.
+ */
+ public static final class Description {
+ public final boolean useAnExistingEcoreModel;
+ public final IPath ecoreModelFilePath;
+ public final String ecorePackageName;
+ public final boolean createRepresentation;
+ public final boolean activateJava;
+
+ /**
+ * @param useAnExistingEcoreModel
+ * Whether the user wants to reuse an existing Ecore model
+ * @param ecoreModelFilePath
+ * The path to the existing Ecore model (can be null if the user does not want to use one)
+ * @param ecorePackageName
+ * The name of the EPackage to create (can be null if the user does not want to create a new Ecore model)
+ * @param createRepresentation
+ * Whether the user wants a Sirius representation to be created for the Ecore model
+ * @param activateJava
+ * Whether the user wants to create Java services
+ */
+ public Description(boolean useAnExistingEcoreModel, IPath ecoreModelFilePath, String ecorePackageName, boolean createRepresentation, boolean activateJava) {
+ this.useAnExistingEcoreModel = useAnExistingEcoreModel;
+ this.ecoreModelFilePath = ecoreModelFilePath;
+ this.ecorePackageName = ecorePackageName;
+ this.createRepresentation = createRepresentation;
+ this.activateJava = activateJava;
+ }
+
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/wizards/NewAleProjectConfigurationWizardPage.java b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/wizards/NewAleProjectConfigurationWizardPage.java
new file mode 100644
index 000000000..c0ff01217
--- /dev/null
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/wizards/NewAleProjectConfigurationWizardPage.java
@@ -0,0 +1,347 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Inria and 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:
+ * Inria - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.ecoretools.ale.ide.ui.wizards;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.layout.LayoutConstants;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.sirius.business.api.dialect.DialectManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog;
+
+/**
+ * Wizard page used to configure an ALE project.
+ */
+public class NewAleProjectConfigurationWizardPage extends WizardPage {
+ /**
+ * Key used by SWTBot to reliably identify widgets.
+ */
+ private static final String SWTBOT_ID = "org.eclipse.swtbot.widget.key";
+ /**
+ * Id used by SWTBot to identify the "create new Ecore model" radio button.
+ */
+ public static final String CREATE_ECORE_MODEL_BUTTON_ID = "createEcoreModelRadioButton";
+ /**
+ * Id used by SWTBot to identify the "Package name" text field.
+ */
+ public static final String ECORE_PACKAGE_NAME_TEXT_ID = "ecorePackageName";
+
+ /**
+ * Whether the "Create a new Ecore model" option should be activated by default.
+ */
+ private static final boolean CREATE_NEW_ECORE_MODEL_BY_DEFAULT = true;
+ /**
+ * Whether the "Create a Sirius representation" option should be activated by default.
+ */
+ private static final boolean CREATE_SIRIUS_REPRESENTATION_BY_DEFAULT = true;
+ /**
+ * Whether the "This project will expose Java services" option should be activated by default.
+ */
+ private static final boolean EXPOSE_JAVA_SERVICES_BY_DEFAULT = false;
+
+ /**
+ * Whether the user wants to use an existing Ecore model instead of creating a new one.
+ */
+ private Button useAnExistingEcoreModelFileRadioButton;
+ /**
+ * URI to the Ecore model chosen by the user (if he wants to reuse an existing one).
+ */
+ private Text selectedEcoreModelText;
+ /**
+ * Name of the new Ecore model (if the user wants to create a new one).
+ */
+ private Text ecorePackageNameText;
+ /**
+ * Whether the user wants a Sirius representation to be created for the Ecore model.
+ */
+ private Button createRepresentationCheckBox;
+ /**
+ * Whether the user plans to expose Java services.
+ */
+ private Button useJavaServicesCheckBox;
+ /**
+ * Whether this page has been visible at least once.
+ */
+ private boolean hasBeenSeen = false;
+
+ /**
+ * Instantiates a new wizard page aimed at configuring an ALE project
+ */
+ protected NewAleProjectConfigurationWizardPage() {
+ super("New ALE Project");
+ setTitle("New ALE Project");
+ setDescription("Configure the project.");
+ }
+
+ /**
+ * @return whether the user wants to reuse an existing Ecore model
+ */
+ public boolean useExistingEcoreModel() {
+ if (useAnExistingEcoreModelFileRadioButton == null) {
+ return false;
+ }
+ return useAnExistingEcoreModelFileRadioButton.getSelection();
+ }
+
+ /**
+ * @return the path to the Ecore model to reuse (meaningful when the user wants
+ * to reuse an existing one)
+ */
+ public IPath getEcoreModelFile() {
+ if (selectedEcoreModelText == null) {
+ return Path.EMPTY;
+ }
+ return new Path(selectedEcoreModelText.getText());
+ }
+
+ /**
+ * @return the name of the new EPackage to create (meaningful when the user wants
+ * to create a new Ecore model)
+ */
+ public String getEcorePackageName() {
+ if (ecorePackageNameText == null) {
+ return "";
+ }
+ return ecorePackageNameText.getText().trim();
+ }
+
+ /**
+ * @return whether the user wants a Sirius representation to be created for the Ecore model
+ */
+ public boolean createRepresentation() {
+ if (createRepresentationCheckBox == null) {
+ return false;
+ }
+ return createRepresentationCheckBox.getSelection();
+ }
+
+ /**
+ * @return whether the user wants to expose Java services in the new project
+ */
+ public boolean activateJava() {
+ if (useJavaServicesCheckBox == null) {
+ return false;
+ }
+ return useJavaServicesCheckBox.getSelection();
+ }
+
+ public void setDefaultEcorePackage(String projectName) {
+ ecorePackageNameText.setText(projectName);
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout();
+ container.setLayout(layout);
+ container.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ createEcoreModelGroup(container);
+ createRepresentationGroup(container);
+ createServicesGroup(container);
+
+ setControl(container);
+ }
+
+ @Override
+ public void setVisible(boolean isVisible) {
+ super.setVisible(isVisible);
+ if (isVisible) {
+ hasBeenSeen = true;
+ ecorePackageNameText.setFocus();
+ setPageComplete(isValid());
+ }
+ }
+
+ /**
+ * Creates widgets allowing the user to select the king of Ecore model he wants.
+ */
+ private void createEcoreModelGroup(Composite container) {
+ Group group = new Group(container, SWT.LEFT);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 3;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+ group.setText("Ecore model");
+
+ // "(x) Create a new Ecore model" button
+
+ Button createNewEcoreModelFileRadioButton = new Button(group, SWT.RADIO | SWT.LEFT);
+ createNewEcoreModelFileRadioButton.setText("Create a new Ecore model");
+ createNewEcoreModelFileRadioButton.setToolTipText("A new Ecore model will be created in the project");
+ createNewEcoreModelFileRadioButton.setSelection(CREATE_NEW_ECORE_MODEL_BY_DEFAULT);
+ createNewEcoreModelFileRadioButton.setData(SWTBOT_ID, CREATE_ECORE_MODEL_BUTTON_ID);
+
+ // Span the button horizontally
+ GridData createNewEcoreModelFileLayoutData = new GridData(GridData.FILL_HORIZONTAL);
+ createNewEcoreModelFileLayoutData.horizontalSpan = layout.numColumns;
+ createNewEcoreModelFileRadioButton.setLayoutData(createNewEcoreModelFileLayoutData);
+
+ // Ecore package name text field
+
+ Label ecorePackageNameLabel = new Label(group, SWT.LEFT);
+ ecorePackageNameLabel.setText("Package name: ");
+
+ // Span the text displaying the path horizontally
+ GridData ecorePackageNameLayoutData = new GridData();
+ ecorePackageNameLayoutData.horizontalIndent = LayoutConstants.getIndent();
+ ecorePackageNameLabel.setLayoutData(ecorePackageNameLayoutData);
+
+ ecorePackageNameText = new Text(group, SWT.BORDER);
+ // Span the text horizontally
+ GridData ecorePackageNameTextLayoutData = new GridData(GridData.FILL_HORIZONTAL);
+ ecorePackageNameTextLayoutData.horizontalSpan = layout.numColumns - 1;
+ ecorePackageNameText.setLayoutData(ecorePackageNameTextLayoutData);
+ ecorePackageNameText.setData(SWTBOT_ID, ECORE_PACKAGE_NAME_TEXT_ID);
+ ecorePackageNameText.addListener(SWT.Modify, e -> setPageComplete(isValid()));
+
+ // "( ) Use an existing Ecore model" button
+
+ useAnExistingEcoreModelFileRadioButton = new Button(group, SWT.RADIO | SWT.LEFT);
+ useAnExistingEcoreModelFileRadioButton.setText("Use an existing Ecore model");
+ useAnExistingEcoreModelFileRadioButton.setText("The project will be configured to reuse an existing Ecore model");
+ useAnExistingEcoreModelFileRadioButton.setSelection(! CREATE_NEW_ECORE_MODEL_BY_DEFAULT);
+
+ // Span the button horizontally
+ GridData useAnExistingEcoreModelFileLayoutData = new GridData(GridData.FILL_HORIZONTAL);
+ useAnExistingEcoreModelFileLayoutData.horizontalSpan = layout.numColumns;
+ useAnExistingEcoreModelFileRadioButton.setLayoutData(useAnExistingEcoreModelFileLayoutData);
+
+ // "Browse Ecore models in workspace" button
+ // Clicking the button opens a dialog that fills 'selectedEcoreModelText' text field
+
+ Button browseExistingEcoreButton = new Button(group, SWT.NONE);
+ browseExistingEcoreButton.setText("Select model...");
+ browseExistingEcoreButton.setToolTipText("Opens a dialog to select an .ecore file within the workspace");
+ browseExistingEcoreButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ FilteredResourcesSelectionDialog dialog = new FilteredResourcesSelectionDialog(
+ shell,
+ false,
+ ResourcesPlugin.getWorkspace().getRoot(),
+ IResource.FILE
+ );
+ dialog.setTitle("Select an Ecore model");
+ dialog.setInitialPattern("*.ecore");
+
+ dialog.open();
+ Object[] selected = dialog.getResult();
+
+ if (selected != null && selected.length == 1 && selected[0] instanceof IResource) {
+ IResource selectedModel = (IResource) selected[0];
+ selectedEcoreModelText.setText("" + selectedModel.getFullPath().toPortableString());
+ }
+ }
+ });
+ GridData browseExistingEcoreLayoutData = new GridData();
+ browseExistingEcoreLayoutData.horizontalIndent = LayoutConstants.getIndent();
+ browseExistingEcoreButton.setLayoutData(browseExistingEcoreLayoutData);
+
+ // Text displaying the path to the Ecore model to reuse
+
+ selectedEcoreModelText = new Text(group, SWT.READ_ONLY | SWT.BORDER);
+ selectedEcoreModelText.addListener(SWT.Modify, e -> {
+ // Make sure "Use an existing Ecore model" button is checked when the user chooses one
+ createNewEcoreModelFileRadioButton.setSelection(false);
+ useAnExistingEcoreModelFileRadioButton.setSelection(true);
+ setPageComplete(isValid());
+ });
+
+ // Span the text displaying the path horizontally
+ GridData selectedEcoreModelTextLayoutData = new GridData(GridData.FILL_HORIZONTAL);
+ selectedEcoreModelText.setLayoutData(selectedEcoreModelTextLayoutData);
+ }
+
+ /**
+ * Creates widgets allowing the user to customize Ecore model's Sirius representation.
+ */
+ private void createRepresentationGroup(Composite container) {
+ Group group = new Group(container, SWT.LEFT);
+ GridLayout layout = new GridLayout();
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+ group.setText("Representation");
+
+ if (SiriusPluginsAreAvailable()) {
+ // "[x] Create a Sirius representation" button
+
+ createRepresentationCheckBox = new Button(group,SWT.CHECK);
+ createRepresentationCheckBox.setText("Create a Sirius representation");
+ createRepresentationCheckBox.setToolTipText("Create an .aird representation file linked to the Ecore model in the model/ folder");
+ createRepresentationCheckBox.setSelection(CREATE_SIRIUS_REPRESENTATION_BY_DEFAULT);
+ }
+ else {
+ Text siriusIsNotAvailableText = new Text(group, SWT.READ_ONLY);
+ siriusIsNotAvailableText.setText("Warning: Sirius UI seems not to be installed, we cannot create any representation.");
+ }
+ }
+
+ @SuppressWarnings({"squid:S00100", "squid:S3516", "squid:S2159"})
+ private static boolean SiriusPluginsAreAvailable() {
+ try {
+ DialectManager.INSTANCE.equals(null);
+ return true;
+ }
+ catch (Exception | NoClassDefFoundError e) {
+ return false;
+ }
+ }
+
+ /**
+ * Creates widgets allowing the user to tailor the services available to ALE.
+ */
+ private void createServicesGroup(Composite container) {
+ Group group = new Group(container, SWT.LEFT);
+ GridLayout layout = new GridLayout();
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+ group.setText("Services");
+
+ // "[ ] Expose Java services" button
+
+ useJavaServicesCheckBox = new Button(group, SWT.CHECK);
+ useJavaServicesCheckBox.setText("This project will expose Java services to ALE");
+ useJavaServicesCheckBox.setToolTipText("Add the Java nature to the project");
+ useJavaServicesCheckBox.setSelection(EXPOSE_JAVA_SERVICES_BY_DEFAULT);
+ }
+
+ /**
+ * @return whether the user has filled in all required info
+ */
+ public boolean isValid() {
+ if (! hasBeenSeen) {
+ return false;
+ }
+ if (useExistingEcoreModel()) {
+ return ! getEcoreModelFile().isEmpty();
+ }
+ else {
+ return ! getEcorePackageName().isEmpty();
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/wizards/NewAleProjectWizard.java b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/wizards/NewAleProjectWizard.java
new file mode 100644
index 000000000..73e8f6ba3
--- /dev/null
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide.ui/src/org/eclipse/emf/ecoretools/ale/ide/ui/wizards/NewAleProjectWizard.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Inria and 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:
+ * Inria - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.ecoretools.ale.ide.ui.wizards;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.ecoretools.ale.ide.project.AleProject;
+import org.eclipse.emf.ecoretools.ale.ide.ui.Activator;
+import org.eclipse.emf.ecoretools.ale.ide.ui.project.WorkspaceAleProject;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.ui.INewWizard;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.dialogs.WizardNewProjectCreationPage;
+
+/**
+ * A wizard used to create new ALE projects.
+ */
+public class NewAleProjectWizard extends Wizard implements INewWizard {
+ /**
+ * Used to choose the name and the location of the project
+ */
+ private WizardNewProjectCreationPage newProjectPage;
+ /**
+ * Used to choose the Ecore model file to use
+ */
+ private NewAleProjectConfigurationWizardPage aleConfigurationPage;
+
+ /**
+ * Instantiates a wizard used to create new ALE projects.
+ */
+ public NewAleProjectWizard() {
+ super();
+ setNeedsProgressMonitor(true);
+ setWindowTitle("ALE Project Wizard");
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ // nothing to initialize
+ }
+
+ @Override
+ public void addPages() {
+ super.addPages();
+
+ newProjectPage = new WizardNewProjectCreationPage("ALE Project");
+ newProjectPage.setTitle("New ALE Project");
+ newProjectPage.setDescription("Create a new ALE project.");
+
+ aleConfigurationPage = new NewAleProjectConfigurationWizardPage();
+
+ addPage(newProjectPage);
+ addPage(aleConfigurationPage);
+ }
+
+
+ @Override
+ public IWizardPage getNextPage(IWizardPage page) {
+ if (page == newProjectPage) {
+ aleConfigurationPage.setDefaultEcorePackage(newProjectPage.getProjectName());
+ }
+ return super.getNextPage(page);
+ }
+
+ @Override
+ public boolean canFinish() {
+ return ! newProjectPage.getProjectName().isEmpty()
+ && ! newProjectPage.getLocationPath().isEmpty()
+ && aleConfigurationPage.isValid();
+ }
+
+ @Override
+ public boolean performFinish() {
+ try {
+ // Gather all the data required to create the project.
+ // Must be retrieved out of the IRunnable to prevent invalid thread accesses
+
+ String projectName = newProjectPage.getProjectName();
+ boolean defaultProjectLocation = newProjectPage.useDefaults();
+ IPath projectLocation;
+
+ if (defaultProjectLocation) {
+ projectLocation = newProjectPage.getLocationPath().append(projectName);
+ }
+ else {
+ projectLocation = newProjectPage.getLocationPath();
+ }
+
+ boolean useAnExistingEcoreModel = aleConfigurationPage.useExistingEcoreModel();
+ IPath ecoreModelFilePath = aleConfigurationPage.getEcoreModelFile();
+ String ecorePackageName = aleConfigurationPage.getEcorePackageName();
+ boolean createRepresentation = aleConfigurationPage.createRepresentation();
+ boolean activateJava = aleConfigurationPage.activateJava();
+
+ // Create the new project
+
+ getContainer().run(true, false, new IRunnableWithProgress() {
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ try {
+ WorkspaceAleProject.Description desc =
+ new WorkspaceAleProject.Description(useAnExistingEcoreModel, ecoreModelFilePath, ecorePackageName, createRepresentation, activateJava);
+ AleProject project = new WorkspaceAleProject(ResourcesPlugin.getWorkspace(), desc);
+ project.create(projectName, projectLocation, monitor);
+ }
+ catch (Exception e) {
+ throw new InvocationTargetException(e, "An error occurred while creating the new ALE project " + projectName);
+ }
+ }
+ });
+ return true;
+ }
+ catch (InvocationTargetException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ Activator.error("An error occurred while creating the new ALE project", e);
+ showErrorDialog(e, "Project creation failed", e.getMessage());
+ }
+ return false;
+ }
+
+ /**
+ * Shows an error dialog displaying an exception's stack trace.
+ *
+ * @param e
+ * The exception to show.
+ * @param title
+ * The title of the dialog.
+ * @param message
+ * The message of the dialog.
+ */
+ private void showErrorDialog(Exception e, String title, String message) {
+ // Compute a String containing the whole stack trace
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+
+ String trace = sw.toString();
+
+ List childStatuses = new ArrayList<>();
+
+ for (String line : trace.split(System.getProperty("line.separator"))) {
+ childStatuses.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, line));
+ }
+
+ // Create a status owning the String stack trace
+ MultiStatus status = new MultiStatus(
+ Activator.PLUGIN_ID, IStatus.ERROR,
+ childStatuses.toArray(new Status[] {}),
+ e.toString(), e
+ );
+ ErrorDialog.openError(getShell(), title, message, status);
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.ecoretools.ale.ide/META-INF/MANIFEST.MF
index 812a54a1e..c461d23f1 100644
--- a/plugins/org.eclipse.emf.ecoretools.ale.ide/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide/META-INF/MANIFEST.MF
@@ -16,5 +16,6 @@ Import-Package: org.eclipse.emf.common.util,
org.eclipse.emf.ecore.resource.impl
Export-Package: org.eclipse.emf.ecoretools.ale.ide,
org.eclipse.emf.ecoretools.ale.ide.listener,
+ org.eclipse.emf.ecoretools.ale.ide.project,
org.eclipse.emf.ecoretools.ale.ide.resource
Bundle-Vendor: Inria/Obeo
diff --git a/plugins/org.eclipse.emf.ecoretools.ale.ide/src/org/eclipse/emf/ecoretools/ale/ide/project/AleProject.java b/plugins/org.eclipse.emf.ecoretools.ale.ide/src/org/eclipse/emf/ecoretools/ale/ide/project/AleProject.java
new file mode 100644
index 000000000..bb41b5fbe
--- /dev/null
+++ b/plugins/org.eclipse.emf.ecoretools.ale.ide/src/org/eclipse/emf/ecoretools/ale/ide/project/AleProject.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Inria and 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:
+ * Inria - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.ecoretools.ale.ide.project;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * An Eclipse IDE project aimed at storing ALE source files.
+ *
+ * Such a project usually contains:
+ *
+ * - some
*.ale source files,
+ * - a
.dsl description file
+ *
+ * and sometimes:
+ *
+ * - an
.ecore model,
+ * - an
.aird representation file
+ *
+ *
+ */
+public interface AleProject {
+
+ /**
+ * Creates the project.
+ *
+ * @param name
+ * The name of the project
+ * @param path
+ * The path of the project
+ * @param monitor
+ * The monitor used to follow the creation. Can be null.
+ *
+ * @return the new project
+ *
+ * @throws CoreException if the project cannot be created
+ */
+ IProject create(String name, IPath path, IProgressMonitor monitor) throws CoreException;
+
+}
diff --git a/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/META-INF/MANIFEST.MF
index ac5d8dd03..ecaaf0c77 100644
--- a/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/META-INF/MANIFEST.MF
@@ -23,4 +23,6 @@ Require-Bundle: org.junit,
org.eclipse.emf.ecoretools.ale.xtext.ide,
org.eclipse.emf.ecoretools.ale.xtext.ui,
org.eclipse.xtext.ui.testing,
- org.eclipse.sirius.diagram
+ org.eclipse.sirius.diagram,
+ org.eclipse.emf.ecoretools.ale.ide,
+ org.eclipse.jdt.core
diff --git a/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/src/org/eclipse/emf/ecoretools/ale/ide/helloworld/tests/WorkspaceAleProjectTest.java b/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/src/org/eclipse/emf/ecoretools/ale/ide/helloworld/tests/WorkspaceAleProjectTest.java
new file mode 100644
index 000000000..37c6448e4
--- /dev/null
+++ b/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/src/org/eclipse/emf/ecoretools/ale/ide/helloworld/tests/WorkspaceAleProjectTest.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Inria and 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:
+ * Inria - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.ecoretools.ale.ide.helloworld.tests;
+
+import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.emf.common.util.URI.createURI;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecoretools.ale.core.parser.Dsl;
+import org.eclipse.emf.ecoretools.ale.ide.ui.project.WorkspaceAleProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.xtext.ui.XtextProjectHelper;
+import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.google.common.io.CharStreams;
+
+/**
+ * Test the behavior of a {@link WorkspaceAleProject}.
+ *
+ * Ignored because fails on CI.
+ */
+@Ignore
+public class WorkspaceAleProjectTest {
+
+ @Test
+ public void testCreateNewStandaloneProject() throws CoreException, IOException {
+
+ // WHEN: a 'minifsm' ALE project is created
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ WorkspaceAleProject.Description desc = new WorkspaceAleProject.Description(
+ false, null,
+ "fsm",
+ true,
+ true
+ );
+ WorkspaceAleProject project = new WorkspaceAleProject(workspace, desc);
+ project.create("minifsm", workspace.getRoot().getProject("minifsm").getFullPath(), new NullProgressMonitor());
+
+ // AND: the workspace is built
+
+ ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor());
+ IResourcesSetupUtil.reallyWaitForAutoBuild();
+
+ // THEN: a valid ALE project exists
+
+ IProject minifsm = workspace.getRoot().getProject("minifsm");
+
+ assertTrue("The minifsm project should exist", minifsm.exists());
+ assertNotNull("The minifsm project should have the Xtext nature " + XtextProjectHelper.NATURE_ID,
+ minifsm.getNature(XtextProjectHelper.NATURE_ID));
+ assertNotNull("The minifsm project should have the Java nature " + XtextProjectHelper.NATURE_ID,
+ minifsm.getNature(JavaCore.NATURE_ID));
+
+ IFolder modelFolder = minifsm.getFolder("model");
+ assertTrue("model/ should exist", modelFolder.exists());
+ assertTrue("model/minifsm.ecore should exist", modelFolder.getFile("minifsm.ecore").exists());
+ assertTrue("model/minifsm.dsl should exist", modelFolder.getFile("minifsm.dsl").exists());
+ assertTrue("model/minifsm.aird should exist", modelFolder.getFile("minifsm.aird").exists());
+ assertTrue("model/minifsm.ale should exist", modelFolder.getFile("minifsm.ale").exists());
+ assertEquals("model/minifsm.ale file has the wrong content", "behavior fsm;", contentOf(modelFolder.getFile("minifsm.ale")));
+
+ EPackage fsm = loadPackage(modelFolder.getFile("minifsm.ecore"));
+ assertEquals("generated FSM EPackage has the wrong name", "fsm", fsm.getName());
+ assertEquals("generated FSM EPackage has the wrong Ns URI", "http://fsm", fsm.getNsURI());
+ assertEquals("generated FSM EPackage has the wrong Ns prefix", "fsm", fsm.getNsPrefix());
+
+ Dsl dsl = new Dsl(modelFolder.getFile("minifsm.dsl").getContents());
+ assertEquals("model/minifsm.dsl does not have the right syntax", asList(createURI("platform:/resource/minifsm/model/minifsm.ecore", true)), toURIs(dsl.getAllSyntaxes()));
+ assertEquals("model/minifsm.dsl does not have the right semantics", asList(createURI("platform:/resource/minifsm/model/minifsm.ale", true)), toURIs(dsl.getAllSemantics()));
+ }
+
+ private static List toURIs(List uris) {
+ return uris.stream()
+ .map(uri -> createURI(uri, true))
+ .collect(toList());
+ }
+
+ private static EPackage loadPackage(IFile file) {
+ ResourceSet resources = new ResourceSetImpl();
+ Resource model = resources.getResource(URI.createFileURI(file.getLocation().toString()), true);
+ return (EPackage) model.getContents().get(0);
+ }
+
+ private static String contentOf(IFile file) throws CoreException, IOException {
+ InputStream contents = file.getContents();
+ try (InputStreamReader reader = new InputStreamReader(contents)) {
+ return CharStreams.toString(reader).trim();
+ }
+ }
+
+}
diff --git a/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/src/org/eclipse/emf/ecoretools/ale/ide/helloworld/tests/commons/NewAleProjectWizardBot.java b/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/src/org/eclipse/emf/ecoretools/ale/ide/helloworld/tests/commons/NewAleProjectWizardBot.java
new file mode 100644
index 000000000..0766920c9
--- /dev/null
+++ b/tests/org.eclipse.emf.ecoretools.ale.ide.ui.tests/src/org/eclipse/emf/ecoretools/ale/ide/helloworld/tests/commons/NewAleProjectWizardBot.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Inria and 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:
+ * Inria - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.ecoretools.ale.ide.helloworld.tests.commons;
+
+import static org.eclipse.emf.ecoretools.ale.ide.ui.wizards.NewAleProjectConfigurationWizardPage.CREATE_ECORE_MODEL_BUTTON_ID;
+import static org.eclipse.emf.ecoretools.ale.ide.ui.wizards.NewAleProjectConfigurationWizardPage.ECORE_PACKAGE_NAME_TEXT_ID;
+import static org.eclipse.swtbot.swt.finder.waits.Conditions.shellCloses;
+import static org.eclipse.swtbot.swt.finder.waits.Conditions.shellIsActive;
+
+import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
+import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
+
+/**
+ * Bot aimed at easing creation of new ALE projects during UI tests.
+ */
+// TODO Make API fluent to prevent inconsistent order of method calls
+public class NewAleProjectWizardBot {
+
+ private final SWTWorkbenchBot bot;
+
+ private SWTBotShell wizard;
+
+ /**
+ * Instantiates a new bot aimed at easing the creation of new ALE projects.
+ *
+ * @param bot
+ * A valid bot capable of manipulating Eclipse's workbench
+ */
+ public NewAleProjectWizardBot(SWTWorkbenchBot bot) {
+ this.bot = bot;
+ }
+
+ /**
+ * Opens the wizard, sets the name of the project then goes to the second page.
+ *
+ * @param projectName
+ * The name of the project
+ */
+ public void open(String projectName) {
+ bot.menu("File")
+ .menu("New")
+ .menu("Project...")
+ .click();
+ wizard = bot.shell("New Project");
+ wizard.activate();
+ bot.tree()
+ .expandNode("ALE")
+ .select("ALE Project");
+ bot.button("Next >").click();
+ bot.textWithLabel("Project name:").setText(projectName);
+ bot.button("Next >").click();
+
+ // Ensure the wizard is open & visible before moving on
+ bot.waitUntil(shellIsActive("ALE Project Wizard"));
+ }
+
+ /**
+ * Selects the 'create new Ecore model' option.
+ *
+ * @param packageName
+ * The name of the new model to create
+ *
+ * @throws WidgetNotFoundException if the node was not found
+ */
+ public void createNewEcoreModel(String packageName) {
+ bot.radioWithId(CREATE_ECORE_MODEL_BUTTON_ID).click();
+ bot.textWithId(ECORE_PACKAGE_NAME_TEXT_ID).setText(packageName);
+ }
+
+ public boolean canFinish() {
+ return bot.button("Finish").isEnabled();
+ }
+
+ public void finish() {
+ if (! canFinish()) {
+ throw new IllegalStateException("The New ALE Project wizard has been asked to finish but the 'Finish' button is disabled");
+ }
+ bot.button("Finish").click();
+
+ // Ensure the project is fully created before moving on
+ bot.waitUntil(shellCloses(wizard));
+ }
+
+}
\ No newline at end of file