diff --git a/org.moflon.core.feature/feature.xml b/org.moflon.core.feature/feature.xml index 376948d1..9fb9564c 100644 --- a/org.moflon.core.feature/feature.xml +++ b/org.moflon.core.feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/org.moflon.core.releng.updatesite/category.xml b/org.moflon.core.releng.updatesite/category.xml index 1391d423..52c7854f 100644 --- a/org.moflon.core.releng.updatesite/category.xml +++ b/org.moflon.core.releng.updatesite/category.xml @@ -1,6 +1,6 @@ - + diff --git a/org.moflon.core.releng.updatesite/changelog.txt b/org.moflon.core.releng.updatesite/changelog.txt index e33f30a3..2737ddcd 100644 --- a/org.moflon.core.releng.updatesite/changelog.txt +++ b/org.moflon.core.releng.updatesite/changelog.txt @@ -1,3 +1,9 @@ +2018-08-09 eMoflon Core 1.3.0 released + +Integrate support for XCore and improve visualization of metamodels + +* See also https://github.com/eMoflon/emoflon-core/issues?utf8=✓&q=is%3Aissue closed%3A2018-04-10..2018-08-09 + 2018-04-10 eMoflon Core 1.2.0 released Minor improvements: diff --git a/org.moflon.core.releng.updatesite/index.html b/org.moflon.core.releng.updatesite/index.html index 5d50dfe6..a857cfe1 100644 --- a/org.moflon.core.releng.updatesite/index.html +++ b/org.moflon.core.releng.updatesite/index.html @@ -11,8 +11,8 @@ - This is an Eclipse UpdateSite and is not meant to be viewed in a normal browser.
- To install eMoflon, enter this UpdateSite in the Eclipse Update Manager.
- If you do not know what that is then work through this tutorial. + This is an Eclipse update site and is not meant to be viewed in a normal browser.
+ To install eMoflon, enter the URL of this update site in the Eclipse Update Manager.
+ For further help, please consult the Eclipe tutorial on installing new software. \ No newline at end of file diff --git a/org.moflon.core.releng.updatesite/site.xml b/org.moflon.core.releng.updatesite/site.xml index 038b3f67..475c76a8 100644 --- a/org.moflon.core.releng.updatesite/site.xml +++ b/org.moflon.core.releng.updatesite/site.xml @@ -1,6 +1,6 @@ - + diff --git a/org.moflon.core.ui.packageregistration/.classpath b/org.moflon.core.ui.packageregistration/.classpath new file mode 100644 index 00000000..22f30643 --- /dev/null +++ b/org.moflon.core.ui.packageregistration/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.moflon.core.ui.packageregistration/.gitignore b/org.moflon.core.ui.packageregistration/.gitignore new file mode 100644 index 00000000..3d1f6cf4 --- /dev/null +++ b/org.moflon.core.ui.packageregistration/.gitignore @@ -0,0 +1,3 @@ +/.settings/ +/bin +/target \ No newline at end of file diff --git a/org.moflon.core.ui.packageregistration/.project b/org.moflon.core.ui.packageregistration/.project new file mode 100644 index 00000000..f2fa45b9 --- /dev/null +++ b/org.moflon.core.ui.packageregistration/.project @@ -0,0 +1,28 @@ + + + org.moflon.core.ui.packageregistration + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.moflon.core.ui.packageregistration/META-INF/MANIFEST.MF b/org.moflon.core.ui.packageregistration/META-INF/MANIFEST.MF new file mode 100644 index 00000000..991a00af --- /dev/null +++ b/org.moflon.core.ui.packageregistration/META-INF/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: org.moflon.core.ui.packageregistration +Bundle-SymbolicName: org.moflon.core.ui.packageregistration;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.eclipse.emf.ecore;bundle-version="2.11.0", + org.eclipse.core.resources, + org.eclipse.equinox.registry, + org.apache.log4j, + org.eclipse.jface, + org.eclipse.ui.workbench, + org.eclipse.emf.ecore.xmi, + org.moflon.core.ui +Bundle-Vendor: eMoflon Developers diff --git a/org.moflon.core.ui.packageregistration/build.properties b/org.moflon.core.ui.packageregistration/build.properties new file mode 100644 index 00000000..e9863e28 --- /dev/null +++ b/org.moflon.core.ui.packageregistration/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml diff --git a/org.moflon.core.ui.packageregistration/icons/register.png b/org.moflon.core.ui.packageregistration/icons/register.png new file mode 100644 index 00000000..bd198799 Binary files /dev/null and b/org.moflon.core.ui.packageregistration/icons/register.png differ diff --git a/org.moflon.core.ui.packageregistration/plugin.xml b/org.moflon.core.ui.packageregistration/plugin.xml new file mode 100644 index 00000000..40ebaf89 --- /dev/null +++ b/org.moflon.core.ui.packageregistration/plugin.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.moflon.core.ui.packageregistration/src/org/moflon/core/ui/packageregistration/EmfRegistryManager.java b/org.moflon.core.ui.packageregistration/src/org/moflon/core/ui/packageregistration/EmfRegistryManager.java new file mode 100644 index 00000000..d281179e --- /dev/null +++ b/org.moflon.core.ui.packageregistration/src/org/moflon/core/ui/packageregistration/EmfRegistryManager.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2008 The University of York. + * 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: + * Dimitrios Kolovos - initial API and implementation + ******************************************************************************/ +package org.moflon.core.ui.packageregistration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EEnum; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +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.ecore.xmi.impl.XMIResourceFactoryImpl; + +/** + * This class allows to register and manage EMF metamodels + * + * @author Roland Kluge - Initial implementation + * + */ +public class EmfRegistryManager { + private static EmfRegistryManager instance = null; + + private HashMap> managedMetamodels = new HashMap<>(); + + public static EmfRegistryManager getInstance() { + if (instance == null) { + instance = new EmfRegistryManager(); + } + return instance; + } + + public void registerMetamodel(String fileName) throws Exception { + List ePackages = register(URI.createPlatformResourceURI(fileName, true), EPackage.Registry.INSTANCE); + managedMetamodels.put(fileName, ePackages); + } + + // The following methods are taken from org.eclipse.epsilon.emc.emf.EmfUtil + + public static List register(URI uri, EPackage.Registry registry) throws Exception { + return register(uri, registry, true); + } + + /** + * Register all the packages in the metamodel specified by the uri in the + * registry. + * + * @param uri + * The URI of the metamodel + * @param registry + * The registry in which the metamodel's packages are registered + * @param useUriForResource + * If True, the URI of the resource created for the metamodel would + * be overwritten with the URI of the last EPackage in the metamodel. + * @return A list of the EPackages registered. + * @throws Exception + * If there is an error accessing the resources. + */ + public static List register(URI uri, EPackage.Registry registry, boolean useUriForResource) + throws Exception { + + List ePackages = new ArrayList(); + + initialiseResourceFactoryRegistry(); + + ResourceSet resourceSet = new ResourceSetImpl(); + resourceSet.getPackageRegistry().put(EcorePackage.eINSTANCE.getNsURI(), EcorePackage.eINSTANCE); + + Resource metamodel = resourceSet.createResource(uri); + metamodel.load(Collections.EMPTY_MAP); + + setDataTypesInstanceClasses(metamodel); + + Iterator it = metamodel.getAllContents(); + while (it.hasNext()) { + Object next = it.next(); + if (next instanceof EPackage) { + EPackage p = (EPackage) next; + + adjustNsAndPrefix(metamodel, p, useUriForResource); + registry.put(p.getNsURI(), p); + ePackages.add(p); + } + } + + return ePackages; + + } + + private static void initialiseResourceFactoryRegistry() { + final Map etfm = Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap(); + + if (!etfm.containsKey("*")) { + etfm.put("*", new XMIResourceFactoryImpl()); + } + + } + + private static void adjustNsAndPrefix(Resource metamodel, EPackage p, boolean useUriForResource) { + if (p.getNsURI() == null || p.getNsURI().trim().length() == 0) { + if (p.getESuperPackage() == null) { + p.setNsURI(p.getName()); + } else { + p.setNsURI(p.getESuperPackage().getNsURI() + "/" + p.getName()); + } + } + + if (p.getNsPrefix() == null || p.getNsPrefix().trim().length() == 0) { + if (p.getESuperPackage() != null) { + if (p.getESuperPackage().getNsPrefix() != null) { + p.setNsPrefix(p.getESuperPackage().getNsPrefix() + "." + p.getName()); + } else { + p.setNsPrefix(p.getName()); + } + } + } + + if (p.getNsPrefix() == null) + p.setNsPrefix(p.getName()); + if (useUriForResource) + metamodel.setURI(URI.createURI(p.getNsURI())); + } + + protected static void setDataTypesInstanceClasses(Resource metamodel) { + Iterator it = metamodel.getAllContents(); + while (it.hasNext()) { + EObject eObject = (EObject) it.next(); + if (eObject instanceof EEnum) { + // ((EEnum) eObject).setInstanceClassName("java.lang.Integer"); + } else if (eObject instanceof EDataType) { + EDataType eDataType = (EDataType) eObject; + String instanceClass = ""; + if (eDataType.getName().equals("String")) { + instanceClass = "java.lang.String"; + } else if (eDataType.getName().equals("Boolean")) { + instanceClass = "java.lang.Boolean"; + } else if (eDataType.getName().equals("Integer")) { + instanceClass = "java.lang.Integer"; + } else if (eDataType.getName().equals("Float")) { + instanceClass = "java.lang.Float"; + } else if (eDataType.getName().equals("Double")) { + instanceClass = "java.lang.Double"; + } + if (instanceClass.trim().length() > 0) { + eDataType.setInstanceClassName(instanceClass); + } + } + } + } +} diff --git a/org.moflon.core.ui.packageregistration/src/org/moflon/core/ui/packageregistration/RegisterMetamodelHandler.java b/org.moflon.core.ui.packageregistration/src/org/moflon/core/ui/packageregistration/RegisterMetamodelHandler.java new file mode 100644 index 00000000..2e3d59fd --- /dev/null +++ b/org.moflon.core.ui.packageregistration/src/org/moflon/core/ui/packageregistration/RegisterMetamodelHandler.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2008 The University of York. + * 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: + * Dimitrios Kolovos - initial API and implementation + ******************************************************************************/ +package org.moflon.core.ui.packageregistration; + +import java.util.Iterator; + +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.handlers.HandlerUtil; +import org.moflon.core.ui.AbstractCommandHandler; + +/** + * This class provides the front-end capabilities for registering an EMF model. + * + * @author Roland Kluge - Initial implementation + * + */ +public class RegisterMetamodelHandler extends AbstractCommandHandler { + + @Override + public Object execute(final ExecutionEvent event) throws ExecutionException { + final ISelection selection = HandlerUtil.getCurrentSelectionChecked(event); + + if (selection instanceof IStructuredSelection) { + final IStructuredSelection structuredSelection = (IStructuredSelection) selection; + final Iterator it = structuredSelection.iterator(); + while (it.hasNext()) { + final IFile file = (IFile) it.next(); + registerMetamodelInFile(file); + } + } + + return AbstractCommandHandler.DEFAULT_HANDLER_RESULT; + } + + private void registerMetamodelInFile(final IFile file) { + final String fileName = file.getFullPath().toOSString(); + try { + EmfRegistryManager.getInstance().registerMetamodel(fileName); + logger.info("Metamodel " + fileName + " registered successfully"); + } catch (final Exception ex) { + logger.info("Metamodel " + fileName + " could not be registered", ex); + } + } + +} diff --git a/org.moflon.core.ui/META-INF/MANIFEST.MF b/org.moflon.core.ui/META-INF/MANIFEST.MF index 644af445..49e5f9c7 100644 --- a/org.moflon.core.ui/META-INF/MANIFEST.MF +++ b/org.moflon.core.ui/META-INF/MANIFEST.MF @@ -34,4 +34,5 @@ Export-Package: org.moflon.core.ui, Bundle-Activator: org.moflon.core.ui.MoflonCoreUiActivator Bundle-ActivationPolicy: lazy Automatic-Module-Name: org.moflon.core.ui +Import-Package: org.eclipse.emf.edit.domain diff --git a/org.moflon.core.ui/plugin.xml b/org.moflon.core.ui/plugin.xml index 4ba391e5..5bc724e9 100644 --- a/org.moflon.core.ui/plugin.xml +++ b/org.moflon.core.ui/plugin.xml @@ -27,7 +27,7 @@ - + @@ -96,6 +96,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -137,10 +190,26 @@ + + + + + + + + - + @@ -227,6 +296,46 @@ + + + + + + + + + + + + @@ -240,7 +349,9 @@ - - + + + + diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/AbstractCommandHandler.java b/org.moflon.core.ui/src/org/moflon/core/ui/AbstractCommandHandler.java index d0ef2d83..39308255 100644 --- a/org.moflon.core.ui/src/org/moflon/core/ui/AbstractCommandHandler.java +++ b/org.moflon.core.ui/src/org/moflon/core/ui/AbstractCommandHandler.java @@ -105,7 +105,7 @@ protected static IFile getEditedFile(final ExecutionEvent event) { protected void openInEditor(final IFile targetFile) throws CoreException, PartInitException { IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); HashMap map = new HashMap(); - map.put(IMarker.LINE_NUMBER, new Integer(1)); + map.put(IMarker.LINE_NUMBER, Integer.valueOf(1)); IMarker marker; marker = targetFile.createMarker(IMarker.TEXT); diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/AbstractMoflonProjectInfoPage.java b/org.moflon.core.ui/src/org/moflon/core/ui/AbstractMoflonProjectInfoPage.java index 1085617f..252fce78 100644 --- a/org.moflon.core.ui/src/org/moflon/core/ui/AbstractMoflonProjectInfoPage.java +++ b/org.moflon.core.ui/src/org/moflon/core/ui/AbstractMoflonProjectInfoPage.java @@ -43,6 +43,8 @@ public abstract class AbstractMoflonProjectInfoPage extends WizardPage { private Text projectNameTextfield; + private boolean generateDefaultXcore = true; + public AbstractMoflonProjectInfoPage(String name, String title, String desc) { super(name); projectName = ""; @@ -137,6 +139,27 @@ public void widgetDefaultSelected(final SelectionEvent e) { }); defaultLocationCheckbox.setSelection(useDefaultLocation); defaultLocationSelectionChanged(); + + Button generateDefaultXcoreButton = new Button(container, SWT.RADIO); + generateDefaultXcoreButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + generateDefaultXcore = generateDefaultXcoreButton.getSelection(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + generateDefaultXcore = generateDefaultXcoreButton.getSelection(); + } + }); + generateDefaultXcoreButton.setText("Generate default .xcore in project"); + generateDefaultXcoreButton.setSelection(true); + + createDummyLabel(container); + createDummyLabel(container); + + Button generateDefaultEcore = new Button(container, SWT.RADIO); + generateDefaultEcore.setText("Generate default .ecore in project"); } public void createControlsForProjectName(final Composite container) { @@ -209,9 +232,8 @@ public IPath getProjectLocation() { /** * Sets the project path to be used * - * @param projectLocation - * the desired project location. null indicates the - * default location + * @param projectLocation the desired project location. null + * indicates the default location */ private void setProjectLocation(final String projectLocation) { this.projectLocation = projectLocation; @@ -220,8 +242,7 @@ private void setProjectLocation(final String projectLocation) { /** * Sets the given error message to indicate a validation problem * - * @param message - * the message or null if no problems were detected + * @param message the message or null if no problems were detected */ private final void updateStatus(final String message) { setErrorMessage(message); @@ -244,10 +265,8 @@ private void dialogChanged() { * Checks if given name is a valid name for a new project in the current * workspace. * - * @param projectName - * Name of project to be created in current workspace - * @param pluginId - * ID of bundle + * @param projectName Name of project to be created in current workspace + * @param pluginId ID of bundle * @return A status object indicating success or failure and a relevant message. */ private static IStatus validateProjectName(final String projectName) { @@ -270,4 +289,7 @@ private static IStatus validateProjectName(final String projectName) { return new Status(IStatus.OK, pluginId, "Project name is valid"); } + public boolean generateDefaultXCoreFile() { + return generateDefaultXcore; + } } \ No newline at end of file diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/VisualiserUtilities.java b/org.moflon.core.ui/src/org/moflon/core/ui/VisualiserUtilities.java new file mode 100644 index 00000000..2e760599 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/VisualiserUtilities.java @@ -0,0 +1,273 @@ +package org.moflon.core.ui; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.resources.IFile; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EEnumLiteral; +import org.eclipse.emf.ecore.EGenericType; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.edit.domain.EditingDomain; +import org.eclipse.emf.edit.domain.IEditingDomainProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.moflon.core.ui.visualisation.EMoflonVisualiser; + +/** + * Utility class with the intend to support implementations of + * {@link EMoflonVisualiser} in interacting with the Eclipse platform. + * + * @author Johannes Brandt + * + */ +public class VisualiserUtilities { + + /** + *

+ * Checks whether or not the given editor currently has a file open with the + * given file extension. + *

+ * + * @param editor + * whose file input shall be checked for the given file extension + * @param expectedExtension + * that is to be compared with the extension of the currently opened + * file in the editor. If null is given, an empty string + * will be used for comparison. + * @return true: only if... + *
    + *
  • ... the editor is not null
  • + *
  • ... the editor input is not null
  • + *
  • ... the editor input is of type {@link IFileEditorInput}
  • + *
  • ... there is a {@link IFile} opened in the editor
  • + *
  • ... the {@link IFile}'s extension is not null
  • + *
  • ... the {@link IFile}'s extension equals the given one
  • + *
+ * false: otherwise. + */ + public static boolean checkFileExtensionSupport(IEditorPart editor, String expectedExtension) { + if (editor == null || editor.getEditorInput() == null + || !(editor.getEditorInput() instanceof IFileEditorInput)) { + return false; + } + + IFile file = ((IFileEditorInput) editor.getEditorInput()).getFile(); + if (file == null || file.getFileExtension() == null) { + return false; + } + + final String checkedFileExtension = (expectedExtension != null) ? expectedExtension : ""; + return file.getFileExtension().equals(checkedFileExtension); + } + + /** + * Retrieves all Ecore modeling elements from the given editor, if the editor + * handles Ecore modeling elements. + * + * @param editor + * The editor, of which all Ecore modeling elements are to be + * extracted. + * @return null is returned, if the given editor is + * null or does not implement the + * {@link IEditingDomainProvider} interface, and if the + * {@link EditingDomain} or the {@link ResourceSet} of the editor is + * null. If there is a {@link ResourceSet} stored with the + * editor, then all resources will be extracted and the resulting collection + * of {@link EObject}s is returned. This collection can be empty, if no + * {@link EObject}s are stored in the editor's resources, however, no + * null references will be contained. + */ + public static Collection extractEcoreElements(IEditorPart editor) { + if (editor == null || !(editor instanceof IEditingDomainProvider)) { + return null; + } + + IEditingDomainProvider edp = (IEditingDomainProvider) editor; + if (edp.getEditingDomain() == null || edp.getEditingDomain().getResourceSet() == null) { + return null; + } + + return expandResources(edp.getEditingDomain().getResourceSet()); + } + + /** + * Checks, if the given {@link ISelection} object contains an Ecore selection. + * + * An Ecore selection does not contain null references, only + * {@link EObject} and {@link Resource} references. + * + * @param selection + * The selection object which is to be checked. + * @return true if the given selection only contains + * {@link EObject} and {@link Resource} references. false + * otherwise. + */ + public static boolean isEcoreSelection(ISelection selection) { + if (selection == null || !(selection instanceof IStructuredSelection)) { + return false; + } + + List internalSelection = ((IStructuredSelection) selection).toList(); + if (internalSelection == null) { + return false; + } + + for (Object obj : internalSelection) { + // an Ecore selection must not contain null references + if (obj == null) { + return false; + } + // an Ecore selection must contain Resources and EObjects only + if (!(obj instanceof EObject) && !(obj instanceof Resource)) { + return false; + } + } + + return true; + } + + /** + * Retrieves the Ecore selection from the given {@link ISelection}, if possible. + * + *

+ * Note: Calls {@link VisualiserUtilities#isEcoreSelection(ISelection)}, + * to check whether the given selection is an Ecore selection. If not, + * null is returned. + *

+ * + * @param selection + * from which all elements are to be returned, if they are not + * null. The given selection cannot be + * null. + * @return null is returned, if the given selection is not an Ecore + * selection. Otherwise the internal selection is extracted and + * returned. The returned collection can be empty, if the selection is empty. + * The wrapped collection won't contain any null references. + */ + public static Collection extractEcoreSelection(ISelection selection) { + if (!isEcoreSelection(selection)) { + return null; + } + + // retrieve internal selection + List internalSelection = ((IStructuredSelection) selection).toList(); + + // expand resources and add them to result + HashSet result = internalSelection.stream()// + .filter(e -> e != null)// + .filter(Resource.class::isInstance)// + .map(Resource.class::cast)// + .flatMap(VisualiserUtilities::expandResource)// + .collect(Collectors.toCollection(HashSet::new)); + + // add remaining EObjects of selection, apart from those already contained in + // resources, to the result. + internalSelection.stream()// + .filter(e -> e != null)// + .filter(EObject.class::isInstance)// + .map(EObject.class::cast)// + .filter(e -> !result.contains(e))// + .forEach(result::add); + + return result; + } + + /** + * Extracts all {@link EObject} instances from a given {@link ResourceSet}. + * + *

+ * Note: There won't be any null references in the returned + * collection. + *

+ * + *

+ * Note: There won't be any {@link EGenericType}, {@link EEnumLiteral} or + * {@link EDataType} references in the returned collection. + *

+ * + * @param resources + * of which all contained elements are to be returned - cannot be + * null + * @return the {@link EObject} instances contained in each of the given + * resources + */ + public static Collection expandResources(ResourceSet resources) { + return resources.getResources().stream()// + .filter(res -> res != null)// + .flatMap(VisualiserUtilities::expandResource)// + .collect(Collectors.toCollection(HashSet::new)); + } + + /** + * Extracts all {@link EObject} instances from a given {@link Resource}. + * + *

+ * Note: There won't be any null references in the returned + * stream. + *

+ * + *

+ * Note: There won't be any {@link EGenericType}, {@link EEnumLiteral} or + * {@link EDataType} references in the returned stream. + *

+ * + * @param resource + * The resource that is to be expanded. + * @return The stream of {@link EObject} instances in order of expansions. + */ + private static Stream expandResource(Resource resource) { + HashSet elements = new HashSet<>(); + resource.getAllContents().forEachRemaining(elements::add); + return elements.stream()// + .filter(elem -> elem != null)// + .filter(elem -> !(elem instanceof EGenericType))// + .filter(elem -> !(elem instanceof EEnumLiteral))// + .filter(elem -> !(elem instanceof EDataType))// + .filter(elem -> !(elem instanceof Map.Entry)); + } + + /** + * States whether the given collection of elements contains any metamodel elements. + * + * @param elements + * The collection of elements that is to be checked for the existence of + * metamodel elements. Neither this collection, nor any of its elements, must be + * null. + * @return true, if the given collection contains at least one metamodel + * element. false otherwise. + */ + public static boolean hasMetamodelElements(Collection elements) { + return elements.stream()// + .filter(e -> e instanceof EModelElement || e instanceof EGenericType)// + .findAny()// + .isPresent(); + } + + /** + * States whether the given collection of elements contains any model elements. + * + * @param elements + * The collection of elements that is to be checked for the existence of + * model elements. Neither this collection, nor any of its elements, must be + * null. + * @return true, if the given collection contains at least one model + * element. false otherwise. + */ + public static boolean hasModelElements(Collection elements) { + return elements.stream()// + .filter(e -> !(e instanceof EModelElement))// + .filter(e -> !(e instanceof EGenericType))// + .findAny()// + .isPresent(); + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/errorhandling/MultiStatusAwareErrorReporter.java b/org.moflon.core.ui/src/org/moflon/core/ui/errorhandling/MultiStatusAwareErrorReporter.java index b671ed0e..438f22f9 100644 --- a/org.moflon.core.ui/src/org/moflon/core/ui/errorhandling/MultiStatusAwareErrorReporter.java +++ b/org.moflon.core.ui/src/org/moflon/core/ui/errorhandling/MultiStatusAwareErrorReporter.java @@ -56,25 +56,29 @@ public final void report(final IStatus status) { } } } - + /** * Returns the file configured via the constructor + * * @return the file of this reporter */ - public IFile getFile() - { - return file; - } + public IFile getFile() { + return file; + } /** - * This method reports a leaf status (not a {@link MultiStatus}) by creating a marker at the configured file (#getFile()) - * @param status the status to report - * @throws CoreException if creating the marker fails + * This method reports a leaf status (not a {@link MultiStatus}) by creating a + * marker at the configured file (#getFile()) + * + * @param status + * the status to report + * @throws CoreException + * if creating the marker fails */ protected void reportLeafStatus(final IStatus status) throws CoreException { - final IFile file = getFile(); - final IResource markedResource = file.exists() ? file : file.getProject(); - final IMarker validationMarker = markedResource.createMarker(WorkspaceHelper.MOFLON_PROBLEM_MARKER_ID); + final IFile file = getFile(); + final IResource markedResource = file.exists() ? file : file.getProject(); + final IMarker validationMarker = markedResource.createMarker(WorkspaceHelper.MOFLON_PROBLEM_MARKER_ID); validationMarker.setAttribute(IMarker.MESSAGE, status.getMessage()); validationMarker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); validationMarker.setAttribute(IMarker.SEVERITY, @@ -83,10 +87,14 @@ protected void reportLeafStatus(final IStatus status) throws CoreException { } /** - * Converts the severity values from the world of {@link IStatus} to the world of {@link IMarker}. - * @param value the {@link IStatus} severity + * Converts the severity values from the world of {@link IStatus} to the world + * of {@link IMarker}. + * + * @param value + * the {@link IStatus} severity * @return the {@link IMarker} severity - * @throws CoreException if the value cannot be translated + * @throws CoreException + * if the value cannot be translated */ private static final int convertStatusSeverityToEclipseMarkerSeverity(final int value) throws CoreException { if (value >= IStatus.ERROR) { diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/handler/AbbreviateLabelsHandler.java b/org.moflon.core.ui/src/org/moflon/core/ui/handler/AbbreviateLabelsHandler.java new file mode 100644 index 00000000..60529a09 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/handler/AbbreviateLabelsHandler.java @@ -0,0 +1,59 @@ +/** + * + */ +package org.moflon.core.ui.handler; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; +import org.moflon.core.ui.AbstractCommandHandler; +import org.moflon.core.ui.visualisation.Configurator; +import org.moflon.core.ui.visualisation.EMoflonPlantUMLGenerator; + +/** + * Handles the "Abbreviate labels" command. + * + * @author Johannes Brandt + * + */ +public class AbbreviateLabelsHandler extends AbstractCommandHandler { + + private static final Logger logger = LogManager.getLogger(AbbreviateLabelsHandler.class); + + public AbbreviateLabelsHandler() { + super(); + } + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + logger.debug("Executing 'Show Model Details'."); + + // toggle the state of the command and retrieve the old value of the state + // (before it was clicked) + Command command = event.getCommand(); + boolean toggleState = !HandlerUtil.toggleCommandState(command); + + // retrieve / change / set configuration + Configurator.getInstance().setDiagramStyle(EMoflonPlantUMLGenerator.ABBR_LABELS, toggleState); + + // notify for change (indirectly, by setting focus to the editor, listeners are + // fired, which in turn update the PlantUml view + final IEditorPart linkedEditor = HandlerUtil.getActiveEditor(event); + updateEditor(linkedEditor); + + return null; + } + + private void updateEditor(IEditorPart editor) { + if (editor != null) { + editor.setFocus(); + } + else { + logger.warn("Could not find any appropriate editor instance to initiate an update of the PlantUml viewpart."); + } + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/handler/NeighbourhoodStrategyHandler.java b/org.moflon.core.ui/src/org/moflon/core/ui/handler/NeighbourhoodStrategyHandler.java new file mode 100644 index 00000000..958d9e46 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/handler/NeighbourhoodStrategyHandler.java @@ -0,0 +1,97 @@ +/** + * + */ +package org.moflon.core.ui.handler; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; +import org.moflon.core.ui.AbstractCommandHandler; +import org.moflon.core.ui.visualisation.ClassDiagram; +import org.moflon.core.ui.visualisation.Configurator; +import org.moflon.core.ui.visualisation.ObjectDiagram; +import org.moflon.core.ui.visualisation.Configurator.StrategyPart; +import org.moflon.core.ui.visualisation.strategy.ClassDiagramStrategies; +import org.moflon.core.ui.visualisation.strategy.DiagramStrategy; +import org.moflon.core.ui.visualisation.strategy.ObjectDiagramStrategies; + +/** + * Allows to enable the visualisation of the 1-neighbouhood of a selection for + * metamodel and model visualiser. + * + * @author Johannes Brandt + * + */ +public class NeighbourhoodStrategyHandler extends AbstractCommandHandler { + + private static final Logger logger = LogManager.getLogger(NeighbourhoodStrategyHandler.class); + + private DiagramStrategy classToggleActive; + private DiagramStrategy objectToggleActive; + private DiagramStrategy classToggleInactive; + private DiagramStrategy objectToggleInactive; + + public NeighbourhoodStrategyHandler() { + super(); + + // initialize strategies + classToggleActive = ClassDiagramStrategies::expandNeighbourhoodBidirectional; + classToggleInactive = DiagramStrategy.identity(); + objectToggleActive = ObjectDiagramStrategies::expandNeighbourhoodBidirectional; + objectToggleInactive = DiagramStrategy.identity(); + } + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + logger.debug("Executing 'Show Model Details'."); + + // toggle the state of the command and retrieve the old value of the state + // (before it was clicked) + Command command = event.getCommand(); + boolean toggleState = !HandlerUtil.toggleCommandState(command); + + // retrieve / change / set configuration + setStrategy(toggleState); + + // notify for change (indirectly, by setting focus to the editor, listeners are + // fired, which in turn update the PlantUml view + final IEditorPart linkedEditor = HandlerUtil.getActiveEditor(event); + updateView(linkedEditor); + + return null; + } + + /** + * Instructs the configurator to set the desired strategy, depending on the + * specified toggle state. + * + * @param toggleState + * Iff true, the 1-neighbourhood strategy is chosen. + */ + private void setStrategy(boolean toggleState) { + if (toggleState) { + Configurator.getInstance().setDiagramStrategy(ClassDiagram.class, StrategyPart.NEIGHBOURHOOD, + classToggleActive); + Configurator.getInstance().setDiagramStrategy(ObjectDiagram.class, StrategyPart.NEIGHBOURHOOD, + objectToggleActive); + } else { + Configurator.getInstance().setDiagramStrategy(ClassDiagram.class, StrategyPart.NEIGHBOURHOOD, + classToggleInactive); + Configurator.getInstance().setDiagramStrategy(ObjectDiagram.class, StrategyPart.NEIGHBOURHOOD, + objectToggleInactive); + } + } + + private void updateView(IEditorPart editor) { + if (editor != null) { + editor.setFocus(); + } else { + logger.warn( + "Could not find any appropriate editor instance to initiate an update of the PlantUml viewpart."); + } + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/handler/ShowDocumentationHandler.java b/org.moflon.core.ui/src/org/moflon/core/ui/handler/ShowDocumentationHandler.java new file mode 100644 index 00000000..255701f2 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/handler/ShowDocumentationHandler.java @@ -0,0 +1,53 @@ +/** + * + */ +package org.moflon.core.ui.handler; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; +import org.moflon.core.ui.AbstractCommandHandler; +import org.moflon.core.ui.visualisation.Configurator; +import org.moflon.core.ui.visualisation.EMoflonPlantUMLGenerator; + +/** + * @author Johannes Brandt + * + */ +public class ShowDocumentationHandler extends AbstractCommandHandler { + + private static final Logger logger = LogManager.getLogger(ShowModelDetailsHandler.class); + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + logger.debug("Executing 'Show Model Details'."); + + // toggle the state of the command and retrieve the old value of the state + // (before it was clicked) + Command command = event.getCommand(); + boolean toggleState = !HandlerUtil.toggleCommandState(command); + + // retrieve / change / set configuration + Configurator.getInstance().setDiagramStyle(EMoflonPlantUMLGenerator.SHOW_DOCUMENTATION, toggleState); + + // notify for change (indirectly, by setting focus to the editor, listeners are + // fired, which in turn update the PlantUml view + final IEditorPart linkedEditor = HandlerUtil.getActiveEditor(event); + updateEditor(linkedEditor); + + return null; + } + + private void updateEditor(IEditorPart editor) { + if (editor != null) { + editor.setFocus(); + } + else { + logger.warn("Could not find any appropriate editor instance to initiate an update of the PlantUml viewpart."); + } + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/handler/ShowModelDetailsHandler.java b/org.moflon.core.ui/src/org/moflon/core/ui/handler/ShowModelDetailsHandler.java new file mode 100644 index 00000000..8271f6c9 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/handler/ShowModelDetailsHandler.java @@ -0,0 +1,60 @@ +/** + * + */ +package org.moflon.core.ui.handler; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; +import org.moflon.core.ui.AbstractCommandHandler; +import org.moflon.core.ui.visualisation.Configurator; +import org.moflon.core.ui.visualisation.EMoflonPlantUMLGenerator; + +/** + * Handles the "Show Model Details" command. + * + * @author Johannes Brandt + * + */ +public class ShowModelDetailsHandler extends AbstractCommandHandler { + + private static final Logger logger = LogManager.getLogger(ShowModelDetailsHandler.class); + + public ShowModelDetailsHandler() { + super(); + } + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + logger.debug("Executing 'Show Model Details'."); + + // toggle the state of the command and retrieve the old value of the state + // (before it was clicked) + Command command = event.getCommand(); + boolean toggleState = !HandlerUtil.toggleCommandState(command); + + // retrieve / change / set configuration + Configurator.getInstance().setDiagramStyle(EMoflonPlantUMLGenerator.SHOW_MODEL_DETAILS, toggleState); + + // notify for change (indirectly, by setting focus to the editor, listeners are + // fired, which in turn update the PlantUml view + final IEditorPart linkedEditor = HandlerUtil.getActiveEditor(event); + updateEditor(linkedEditor); + + return null; + } + + private void updateEditor(IEditorPart editor) { + if (editor != null) { + editor.setFocus(); + } + else { + logger.warn("Could not find any appropriate editor instance to initiate an update of the PlantUml viewpart."); + } + } + +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ClassDiagram.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ClassDiagram.java new file mode 100644 index 00000000..5cb678ec --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ClassDiagram.java @@ -0,0 +1,61 @@ +/** + * + */ +package org.moflon.core.ui.visualisation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EClass; + +/** + * Represents a class diagram. + * + * The nodes of this diagram type are {@link EClass} instances. + * + * @author Johannes Brandt + * + */ +public class ClassDiagram extends Diagram { + + /** + * Stores the documentation elements an the EClasses they are to be attached to. + */ + protected Map> docToEClasses; + + public ClassDiagram(Collection superset) { + super(superset); + + docToEClasses = new HashMap<>(); + } + + public ClassDiagram(Collection superset, Collection selection) { + super(superset, selection); + + docToEClasses = new HashMap<>(); + } + + /** + * Getter for {@link #docToEClasses}. + * + * @return A map of documentation elements to their respective EClass they are + * attached to. + */ + public Map> getDoumentation() { + return docToEClasses; + } + + /** + * Setter for {@link #docToEClasses}. + * + * @param docToEClasses + * The new mapping of documentation elements to EClasses. + */ + public void setDocumentation(Map> docToEClasses) { + this.docToEClasses = docToEClasses; + } + +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ConfigurableVisualiser.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ConfigurableVisualiser.java new file mode 100644 index 00000000..05092e8a --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ConfigurableVisualiser.java @@ -0,0 +1,85 @@ +package org.moflon.core.ui.visualisation; + +import org.moflon.core.ui.visualisation.Configurator.StrategyPart; +import org.moflon.core.ui.visualisation.strategy.DiagramStrategy; + +/** + * This interface defines the required method signatures for a visualiser (such + * as all classes inheriting from {@link EMoflonVisualiser}) to be configurable. + * + * A visualiser is configurable, if style bits can be set to influence the + * PlantUML DSL code generation, and if a strategy can be set for the + * computation of the diagram nodes and edges that are to be visualised. + * Furthermore, a configurable visualiser is required to implement a check + * method, such that a {@link Configurator} instance might check, whether or not + * a specific diagram type is supported by this visualiser. + * + * @author Johannes Brandt + * + * @param + * The specific type for which a strategy can be applied. Usually + * this type encapsulates all the information necessary to describe a + * diagram that is to be visualised by this visualiser. Examples for + * such a type are {@link Diagram}, and all its inheriting classes, + * such as {@link ClassDiagram} and {@link ObjectDiagram}. However, + * the type parameter does not necessarily have to be or extend + * {@link Diagram}. + */ +public interface ConfigurableVisualiser { + + /** + * Sets the style bits that are to be employed during the PlantUML diagram text + * generation. + * + * Style bits define how a diagram is visualised, e.g. which elements of the + * diagram are shown or in which color. For the declaration of some general + * style bits see {@link EMoflonPlantUMLGenerator}. + * + * @param style + * The style bits. + */ + void setDiagramStyle(int style); + + /** + * Sets a strategy for processing diagrams of type T, i.e. the + * diagram type that is supported by this visualiser. + * + * @param strategy + * The strategy that is to be applied to diagrams handled by this + * visualiser. + */ + void setDiagramStrategy(DiagramStrategy strategy); + + /** + * This method is used by configurator units, such as {@link Configurator}. If + * the given {@link Class} instance encapsulates the diagram type supported by + * this visualiser, this method is expected to return true. This + * method is called by configurators, to ensure, that a specific strategy can be + * applied to this visualiser. + * + * @param diagramClass + * The {@link Class} instance resembling a diagram type, for which it + * is to be checked, whether it is supported by this visualiser. + * @return true, if the given diagram class describes a diagram + * type, which is supported by this visualiser, false + * otherwise. + */ + boolean supportsDiagramType(Class diagramClass); + + /** + * Provides a default strategy for every {@link StrategyPart}. + * + *

+ * Note: The default implementation of this methods returns + * {@link DiagramStrategy#identity()} for every {@link StrategyPart}. + *

+ * + * @param part + * The strategy part, for which a partial default strategy is to be + * returned. + * @return The default strategy for the given strategy part. + */ + default DiagramStrategy getDefaultStrategy(StrategyPart part) { + return DiagramStrategy.identity(); + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/Configurator.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/Configurator.java new file mode 100644 index 00000000..d6b0c24c --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/Configurator.java @@ -0,0 +1,261 @@ +package org.moflon.core.ui.visualisation; + +import java.util.HashMap; +import org.moflon.core.ui.visualisation.strategy.DiagramStrategy; + +/** + * Configures {@link EMoflonVisualiser}s with regard to their diagram processing + * strategy (manipulation of nodes and edges) and diagram style (PlantUML code + * generation). + * + * For an {@link EMoflonVisualiser} to be configured by this class, it needs to + * be implement the {@link ConfigurableVisualiser} interface. Then, the diagram + * style is set for all {@link ConfigurableVisualiser} instances, if it is + * changed via {@link #setDiagramStyle(int, boolean)}. The diagram processing + * strategy (= diagram strategy) can only be applied to visualisers, which + * support a certain diagram type (see + * {@link #setDiagramStrategy(Class, StrategyPart, DiagramStrategy)}. A complete + * diagram strategy consists of multiple partial strategies. Each part of a + * complete diagram strategy is identified by a value of {@link StrategyPart}. + * If one partial strategy is changed via + * {@link #setDiagramStrategy(Class, StrategyPart, DiagramStrategy)}, the + * complete diagram strategy is recomposed and applied to all visualisers, which + * support the diagram type associated with the strategy. + * + *

+ * Note: This class is singleton. + *

+ * + * @author Johannes Brandt + * + */ +public class Configurator { + + /** + * Used to identify a specific part of a strategy. + * + * A strategy, as implied by this enum, consists of two parts, initialization + * and neighbourhood calculation. The first is associated with + * {@link StrategyPart#INIT}, the latter with + * {@link StrategyPart#NEIGHBOURHOOD}. Therefore, this enum enables e.g. command + * handlers to call + * {@link Configurator#setDiagramStrategy(Class, StrategyPart, DiagramStrategy)}, + * and precisely state which part of the strategy they wish to change. + * + * @author Johannes Brandt + * + */ + public static enum StrategyPart { + // TODO: Improve mechanism to support the isolation of strategy parts, which are + // defined elsewhere, i.e. make the configurator "unaware" of the specific + // strategy parts. + INIT, NEIGHBOURHOOD; + } + + /** + * The monitor used to synchronize access to visualiser style or strategy. + */ + private static final Object MONITOR = new Object(); + + /** + * Stores all {@link EMoflonVisualiser} instances registered to this Eclipse + * Platform. + */ + private HashMap, EMoflonVisualiser> visualisers; + + /** + * Style bits for the {@link EMoflonVisualiser}s. + */ + private int style = 1; + + /** + * Stores the strategy for the part {@link StrategyPart#INIT} for each diagram + * type. + */ + private HashMap, DiagramStrategy> diagramTypeToInitStrategy; + + /** + * Stores the strategy for the part {@link StrategyPart#NEIGHBOURHOOD} for each + * diagram type. + */ + private HashMap, DiagramStrategy> diagramTypeToNeighbourhoodStrategy; + + /** + * Provides the {@link Configurator} singleton instance. + */ + private static class InstanceHolder { + private static final Configurator INSTANCE = new Configurator(); + } + + /** + * Private constructor to prevent instance creation. + */ + private Configurator() { + visualisers = new HashMap<>(); + diagramTypeToInitStrategy = new HashMap<>(); + diagramTypeToNeighbourhoodStrategy = new HashMap<>(); + } + + /** + * Provides thread-safe singleton access. + * + * @return The singleton instance for {@link Configurator}. + */ + public static Configurator getInstance() { + return InstanceHolder.INSTANCE; + } + + /** + * Registers a visualiser for configuration. + * + * Any {@link EMoflonVisualiser} can be registered with this configurator, such + * that diagram style and strategy configuration can be passed that visualiser. + * + *

+ * Note: For an {@link EMoflonVisualiser} to be actually handled in terms + * of diagram style and diagram strategy configuration, it needs to implement + * the {@link ConfigurableVisualiser} interface. + *

+ * + * @param eMoflonVisualiser + * The visualiser that is to be configured by this configurator. + */ + public void registerVisualiser(EMoflonVisualiser eMoflonVisualiser) { + synchronized (MONITOR) { + visualisers.put(eMoflonVisualiser.getClass(), eMoflonVisualiser); + } + } + + /** + * Sets the given style bits in the current diagram style and applies them to + * all registered visualisers. + * + * @param styleBits + * The style bits that are to be set or unset. + * @param doSet + * If true, the 1-bits in styleBits are set in the + * diagram style, otherwise the 1-bits in styleBits are unset in the + * diagram style. + */ + public void setDiagramStyle(int styleBits, boolean doSet) { + synchronized (MONITOR) { + if (doSet) { + style |= styleBits; + } else { + style &= ~styleBits; + } + applyStyle(style); + } + } + + /** + * Sets the given strategy for the specified strategy part and diagram class, + * composes the full strategy, and applies it to all registered visualisers, + * that support the given diagram clas. + * + * @param diagramClass + * The diagram class for which this partial strategy is to be set. + * @param part + * Identifies the part of a full strategy that is to be set. + * @param strategy + * The partial strategy, that is to be set and applied to all + * registered and supported visualisers. + */ + @SuppressWarnings("unchecked") + public void setDiagramStrategy(Class diagramClass, StrategyPart part, + DiagramStrategy strategy) { + if (diagramClass == null || part == null || strategy == null) { + return; + } + + synchronized (MONITOR) { + switch (part) { + case INIT: + if (!diagramTypeToNeighbourhoodStrategy.containsKey(diagramClass)) { + diagramTypeToNeighbourhoodStrategy.put(diagramClass, + getDefaultStrategy(diagramClass, StrategyPart.NEIGHBOURHOOD)); + } + diagramTypeToInitStrategy.put(diagramClass, strategy); + break; + case NEIGHBOURHOOD: + if (!diagramTypeToInitStrategy.containsKey(diagramClass)) { + diagramTypeToInitStrategy.put(diagramClass, getDefaultStrategy(diagramClass, StrategyPart.INIT)); + } + diagramTypeToNeighbourhoodStrategy.put(diagramClass, strategy); + break; + default: + return; + } + + DiagramStrategy fullStrategy = (DiagramStrategy) diagramTypeToInitStrategy.get(diagramClass); + fullStrategy = fullStrategy + .andThen((DiagramStrategy) diagramTypeToNeighbourhoodStrategy.get(diagramClass)); + applyStrategy(diagramClass, fullStrategy); + } + } + + /** + * Applies the given diagram style to all registered {@link EMoflonVisualiser}s. + * + * @param style + * The style that is to be applied. + */ + private void applyStyle(int style) { + synchronized (MONITOR) { + visualisers.values().stream()// + .filter(ConfigurableVisualiser.class::isInstance)// + .map(ConfigurableVisualiser.class::cast)// + .forEach(configurable -> configurable.setDiagramStyle(style)); + } + } + + /** + * Used to determine the default partial strategies for all registered + * {@link EMoflonVisualiser}s, which support a specified diagram class. + * + * @param diagramClass + * The diagram class that needs to be supported by at least one of + * the visualisers. + * @param part + * The identifier for the part of a strategy, for which a default + * implementation is wanted. + * @return The default implementation for the partial strategy, which supports + * the processing of the specified diagram class. + */ + @SuppressWarnings("unchecked") + private DiagramStrategy getDefaultStrategy(Class diagramClass, StrategyPart part) { + // TODO: Improve mechanism. Only one visualiser gets to determine the default + // strategy. If there are multiple visualisers supporting the same diagram type, + // then this implementation returns the first. Any other is not taken into + // consideration. + return visualisers.values().stream()// + .filter(ConfigurableVisualiser.class::isInstance)// + .map(ConfigurableVisualiser.class::cast)// + .filter(cVis -> cVis.supportsDiagramType(diagramClass))// + .map(cVis -> cVis.getDefaultStrategy(part))// + .findFirst()// + .orElse(DiagramStrategy.identity()); + } + + /** + * Applies the specified strategy to all visualisers that support the given + * diagram class. + * + * @param diagramClass + * Identifies the diagram type that is supported by the given + * strategy. + * @param strategy + * The strategy that is to be applied to all visualisers, which + * support the specified diagram class. + */ + @SuppressWarnings("unchecked") + private void applyStrategy(Class diagramClass, DiagramStrategy strategy) { + synchronized (MONITOR) { + visualisers.values().stream()// + .filter(ConfigurableVisualiser.class::isInstance)// + .map(ConfigurableVisualiser.class::cast)// + .filter(cVis -> cVis.supportsDiagramType(diagramClass))// + .forEach(cVis -> cVis.setDiagramStrategy(strategy)); + } + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/Diagram.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/Diagram.java new file mode 100644 index 00000000..43c889cf --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/Diagram.java @@ -0,0 +1,137 @@ +/** + * + */ +package org.moflon.core.ui.visualisation; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Represents a generic diagram. + * + * Stores nodes and the edges connecting them. Edges are represented by + * {@link VisualEdge} instances. + * + * @author Johannes Brandt + * + * @param + * The node type. + */ +public class Diagram { + + /** + * Stores all elements that may be visualised. + */ + final protected Collection superset; + + /** + * Stores all selected elements of the diagram. Should be distinct from + * {@link Diagram#neighbourhood}. + */ + protected Collection selection; + + /** + * Stores the neighbourhood of {@link Diagram#selection}. Should be distinct + * from {@link Diagram#selection}. + */ + protected Collection neighbourhood; + + /** + * Stores the edges that are to be visualised. + */ + protected Collection edges; + + /** + * Parameterized constructor. + * + * @param superset + * All elements of the diagram. Cannot be changed once initialized. + */ + public Diagram(Collection superset) { + this.superset = superset; + this.selection = new HashSet<>(); + this.neighbourhood = new HashSet<>(); + this.edges = new HashSet<>(); + } + + /** + * Parameterized constructor. + * + * @param superset + * All elements of the diagram. Cannot be changed once initialized. + * @param selection + * The selected elements of the diagram. + */ + public Diagram(Collection superset, Collection selection) { + this.superset = superset; + this.selection = selection; + this.neighbourhood = new HashSet<>(); + this.edges = new HashSet<>(); + } + + /** + * Getter for {@link Diagram#superset}. + * + * @return All nodes of the diagram. + */ + public Collection getSuperset() { + return superset; + } + + /** + * Getter for {@link Diagram#selection}. + * + * @return The selected nodes of the diagram. + */ + public Collection getSelection() { + return selection; + } + + /** + * Setter for {@link Diagram#selection}. + * + * @param selection + * The new selection of nodes. + */ + public void setSelection(Collection selection) { + this.selection = selection; + } + + /** + * Getter for {@link Diagram#neighbourhood}. + * + * @return All nodes that are considered to be in the neighbourhood. + */ + public Collection getNeighbourhood() { + return neighbourhood; + } + + /** + * Setter for {@link Diagram#neighbourhood}. + * + * @param neighbourhood + * The new neighbourhood. + */ + public void setNeighbourhood(Collection neighbourhood) { + this.neighbourhood = neighbourhood; + } + + /** + * Getter for {@link #edges}. + * + * @return The edges of this diagram. + */ + public Collection getEdges() { + return edges; + } + + /** + * Setter for {@link #edges}. + * + * @param edges + * The new edges for this diagram. + */ + public void setEdges(Collection edges) { + this.edges = edges; + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonEcoreVisualiser.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonEcoreVisualiser.java new file mode 100644 index 00000000..abafdac3 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonEcoreVisualiser.java @@ -0,0 +1,191 @@ +package org.moflon.core.ui.visualisation; + +import java.util.Collection; +import java.util.HashSet; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jdt.core.dom.PrimitiveType.Code; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IEditorPart; +import org.moflon.core.ui.VisualiserUtilities; +import org.moflon.core.ui.visualisation.strategy.DiagramStrategy; + +/** + * Abstract implementation for the visualisation of Ecore metamodels and models. + * + * @author Johannes Brandt (initial contribution) + * + */ +public abstract class EMoflonEcoreVisualiser extends EMoflonVisualiser implements ConfigurableVisualiser { + + /** + * Stores whether or not the superset of Ecore elements can be retrieved from + * currently associated editor. + */ + private boolean isEmptySelectionSupported = false; + + /** + * Stores a subset of Ecore elements, that are to be visualised.. + */ + private Collection latestSelection; + + /** + * Resembles the superset of all elements that could potentially be visualised. + * Typically determined by all the elements currently handled by the associated + * editor. + */ + private Collection allElements; + + /** + * Diagram style bits - used in PlantUML diagram text generation. + */ + protected int style = EMoflonPlantUMLGenerator.SHOW_MODEL_DETAILS; + + /** + * Allows chained operations on a diagram with node type {@link Code T}. Should + * at least be {@link UnaryOperator#identity()}. + */ + protected DiagramStrategy strategy; + + @Override + public boolean supportsEditor(IEditorPart editor) { + // check if editor currently has Ecore related model loaded + boolean hasEcoreFileLoaded = VisualiserUtilities.checkFileExtensionSupport(editor, "ecore") + || VisualiserUtilities.checkFileExtensionSupport(editor, "xmi"); + // || VisualiserUtilities.checkFileExtensionSupport(editor, "genmodel"); + + // Check if the editor internally handles Ecore EObjects. + // Since some editors allow to load both .ecore and .xmi Resources at the same + // time, it is not possible to check for specific elements from Ecore metamodels + // or models. This has to be done in #supportsSelection(...), when it is clear + // whether the selection is empty or not. + allElements = VisualiserUtilities.extractEcoreElements(editor); + latestSelection = allElements; + isEmptySelectionSupported = allElements != null; + + // if only one of the above conditions is true, there is still a possibility + // that a given selection might be supported + return hasEcoreFileLoaded || isEmptySelectionSupported; + } + + @Override + public boolean supportsSelection(ISelection selection) { + // only Ecore selections are supported + if (!VisualiserUtilities.isEcoreSelection(selection)) { + return false; + } + + // empty Ecore selections are supported only if the editor can provide Ecore + // elements, this is checked and remembered in supportsEditor(...) + Collection ecoreSelection = VisualiserUtilities.extractEcoreSelection(selection); + if (ecoreSelection == null || ecoreSelection.isEmpty()) { + return false; + } + latestSelection = ecoreSelection; + // in case no elements can be extracted from the editor, allElements is set to + // the selection + if (!isEmptySelectionSupported) { + allElements = ecoreSelection; + } + + boolean isSupported = supportsSelection(latestSelection); + return isSupported; + } + + @Override + public String getDiagramBody(IEditorPart editor, ISelection selection) { + // In order to save processing time latestSelection already contains the + // best fit for an ecore selection and does not have to be recalculated from + // editor and selection. It is determined during the calls of + // supportsEdidtor(...) and supportsSelection(...). + // Note that if a specific selection is null, empty, or simply not supported, + // latestSelection is supposed to contain the whole editor output. + // This in turn can be again null, empty or not supported, because the check for + // editor support is quite tolerant. This is why this has to be checked here + // again. + String result = EMoflonPlantUMLGenerator.emptyDiagram(); + if (latestSelection == null || latestSelection.isEmpty()) { + return result; + } + if (VisualiserUtilities.hasMetamodelElements(latestSelection) + && VisualiserUtilities.hasModelElements(latestSelection)) { + return result; + } + + synchronized (this) { + result = getDiagramBody(latestSelection); + } + return result; + } + + @Override + public synchronized void setDiagramStyle(int style) { + this.style = style; + } + + @Override + public synchronized void setDiagramStrategy(DiagramStrategy strategy) { + if (strategy == null) { + throw new IllegalArgumentException("Strategy cannot be null!"); + } + this.strategy = strategy; + } + + /** + * Checks whether or not a given Ecore selection is supposed to be supported by + * this Ecore visualiser. + * + * @param selection + * All Ecore elements that are supposed to be visualised. + * @return true if the given selection is supported, otherwise + * false. + */ + protected abstract boolean supportsSelection(Collection selection); + + /** + * Calculates the diagram text for the given Ecore elements. + * + * @param elements + * The Ecore elements that are to be visualised. + * @return The generated diagram text describing the given elements using the + * PlantUML DSL. + */ + protected abstract String getDiagramBody(Collection elements); + + /** + * Getter for {@link #allElements}. + * + * @return All elements that can potentially be visualised. + */ + protected Collection getAllElements() { + return allElements; + } + + /** + * For a given list of {@link VisualEdge} instances, return only one + * {@link VisualEdge} for every bidirectional association. + * + * For example, if two classes cl1 and cl2 do share a bidirectional association + * cl1 <-> cl2, only one {@link VisualEdge} instance will be returned, + * describing only one navigation direction, e.g. cl1 <- cl2. In short, for + * every bidirectional association, the opposing edge will not be returned. No + * guarantees can be made which direction of a bidirectional association will be + * returned. If there is a unidirectional {@link VisualEdge}, it will be + * returned. + * + * @param edges + * The list of edges, each one representing one navigation direction + * of an association, which may contain the opposing navigation + * direction of a bidirectional association. + * @return The list of edges without any opposing navigation direction. + */ + protected Collection handleOpposites(Collection edges) { + HashSet edgesWithoutEOpposite = new HashSet<>(); + for (VisualEdge edge : edges) { + if (!edge.hasEOpposite() || !edgesWithoutEOpposite.contains(edge.findEOpposite(edges).orElse(null))) + edgesWithoutEOpposite.add(edge); + } + + return edgesWithoutEOpposite; + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonMetamodelVisualiser.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonMetamodelVisualiser.java new file mode 100644 index 00000000..4292e5a5 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonMetamodelVisualiser.java @@ -0,0 +1,212 @@ +package org.moflon.core.ui.visualisation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EGenericType; +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.moflon.core.ui.VisualiserUtilities; +import org.moflon.core.ui.visualisation.Configurator.StrategyPart; +import org.moflon.core.ui.visualisation.strategy.ClassDiagramStrategies; +import org.moflon.core.ui.visualisation.strategy.DiagramStrategy; + +/** + * Visualises UML Class Diagrams for Ecore metamodels. + * + */ +public class EMoflonMetamodelVisualiser extends EMoflonEcoreVisualiser { + + private static final String DOCUMENTATION_KEY = "documentation"; + + public EMoflonMetamodelVisualiser() { + super(); + + // set default strategy + strategy = getDefaultStrategy(StrategyPart.INIT)// + .andThen(getDefaultStrategy(StrategyPart.NEIGHBOURHOOD)); + } + + @Override + public boolean supportsSelection(Collection selection) { + // An Ecore metamodel must contain EModelElements only. If it contains other + // elements, the selection is not supported by this visualiser. + return !VisualiserUtilities.hasModelElements(selection); + } + + @Override + public boolean supportsDiagramType(Class diagramClass) { + return ClassDiagram.class == diagramClass; + } + + @Override + public DiagramStrategy getDefaultStrategy(StrategyPart part) { + switch (part) { + case INIT: + return ClassDiagramStrategies::determineEdgesForSelection; + case NEIGHBOURHOOD: + return ClassDiagramStrategies::expandNeighbourhoodBidirectional; + default: + return super.getDefaultStrategy(part); + } + } + + @Override + protected String getDiagramBody(Collection selection) { + HashSet allClasses = getAllElements().stream()// + .filter(EClass.class::isInstance)// + .map(EClass.class::cast)// + .collect(Collectors.toCollection(HashSet::new)); + + // For every selected EModelElement choose an appropriate EClass to represent + // it. + Collection chosenClasses = resolveSelection(selection); + + // Create diagram and process it using the defined strategy. + ClassDiagram diagram = strategy.apply(new ClassDiagram(allClasses, chosenClasses)); + diagram.setEdges(handleOpposites(diagram.getEdges())); + + // extract and resolve documentation for selected AND neighboured EClasses + HashSet chosenAnnotations = getAllElements().stream()// + .filter(EAnnotation.class::isInstance)// + .map(EAnnotation.class::cast)// + .collect(Collectors.toCollection(HashSet::new)); + diagram.setDocumentation(resolveAnnotations(chosenAnnotations, diagram, selection)); + + return EMoflonPlantUMLGenerator.visualiseEcoreElements(diagram, style); + } + + private Collection resolveSelection(Collection selection) { + HashSet result = new HashSet<>(selection.size()); + + // retrieve classes, and enclosing classes of operations, attributes... + // TODO: resolve EDataType as well? + for (EObject eobject : selection) { + EClass eclass = resolveObject(eobject); + if (eclass != null && !result.contains(eclass)) { + result.add(eclass); + } + } + + // expand EPackages, retrieve classes and add them to result + selection.stream()// + .filter(EPackage.class::isInstance)// + .map(EPackage.class::cast)// + .flatMap(epackage -> Stream.concat(Stream.of(epackage), epackage.getESubpackages().stream()))// + .flatMap(epackage -> epackage.getEClassifiers().stream())// + .filter(EClass.class::isInstance)// + .map(EClass.class::cast)// + .filter(cls -> !result.contains(cls))// + .forEach(result::add); + + return result; + } + + /** + * Finds an EClass instance, which represents the specified EObject. + * + * @param eobject + * The object for which an {@link EClass} representation is required. + * @return The representing {@link EClass}. Typically resolved using the + * containment relation. Is null, iff no representation can + * be found. + */ + private EClass resolveObject(EObject eobject) { + EClass eclass = null; + if (eobject instanceof EClass) { + eclass = (EClass) eobject; + } else if (eobject instanceof EStructuralFeature) { + // EReference and EAttribute + EStructuralFeature efeature = (EStructuralFeature) eobject; + eclass = efeature.getEContainingClass(); + } else if (eobject instanceof EOperation) { + EOperation eoperation = (EOperation) eobject; + eclass = eoperation.getEContainingClass(); + } else if (eobject instanceof EParameter) { + EParameter eparameter = (EParameter) eobject; + eclass = eparameter.getEOperation().getEContainingClass(); + } else if (eobject instanceof EGenericType) { + EGenericType etype = (EGenericType) eobject; + eclass = etype.getEClassifier() instanceof EClass ? (EClass) etype.getEClassifier() : null; + } else if (eobject instanceof EAnnotation) { + EAnnotation eannot = (EAnnotation) eobject; + if (eannot.getDetails().containsKey(DOCUMENTATION_KEY)) { + eclass = resolveObject(eannot.getEModelElement()); + } + } + return eclass; + } + + /** + * Maps annotation elements to the EClass they are attached to, respectively the + * EClass representing the EModelElement the annotation element is attached to. + * + * An EAnnotation from the specified {@code allAnnotations} collection is only + * considered to be added to the returned mapping, if it contains the + * "documentation" key and the corresponding String value. + * + * @param allAnnotations + * The annotations that are to be mapped, if their representing + * EClass is contained in either the diagram selection of + * neighbourhood. + * @param diagram + * The diagram containing the selected and neighbourhood EClasses. + * @param originalSelection + * If no representing EClass can be found, then the original + * selection is checked whether or not the annotation even has to be + * mapped. + * @return The mapping of EAnnotations to the EClasses. The EClass may be + * null, if the EAnnotation is attached to an EPackage, + * i.e. for which no EClass representation can be found. This is why + * EAnnotations are not directly mapped to EClasses, but an Optional. + */ + private Map> resolveAnnotations(Collection allAnnotations, + ClassDiagram diagram, Collection originalSelection) { + Map> result = new HashMap<>(allAnnotations.size()); + Collection chosenClasses = diagram.getSelection(); + Collection chosenNeighbourhood = diagram.getNeighbourhood(); + + for (EAnnotation documenter : allAnnotations) { + if (documenter.getDetails().get(DOCUMENTATION_KEY) != null) { + EClass documentee = resolveObject(documenter); + if (chosenClasses.contains(documentee) || chosenNeighbourhood.contains(documentee) + || isContained(originalSelection, documenter)) { + result.put(documenter, Optional.ofNullable(documentee)); + } + } + } + + return result; + } + + /** + * Checks whether or not the specified element is contained in the specified + * search space. + * + * @param searchSpace + * All EObjects, that will be checked for containment. + * @param element + * The element for which the check is performed. + * @return Result is true, iff any of the EObjects in the search + * space, or one of their sub elements (eContents relation) is the + * specified element. + */ + private boolean isContained(Collection searchSpace, EObject element) { + return searchSpace.stream()// + .flatMap(obj -> Stream.concat(Stream.of(obj), obj.eContents().stream()))// + .filter(obj -> obj != null)// + .filter(obj -> obj == element)// + .findFirst()// + .isPresent(); + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonModelAndMetamodelVisualiser.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonModelAndMetamodelVisualiser.java deleted file mode 100644 index fbdb9d5c..00000000 --- a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonModelAndMetamodelVisualiser.java +++ /dev/null @@ -1,253 +0,0 @@ -package org.moflon.core.ui.visualisation; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.tuple.Pair; -import org.eclipse.emf.common.util.TreeIterator; -import org.eclipse.emf.ecore.EClass; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.EReference; -import org.eclipse.emf.ecore.EStructuralFeature; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.emf.ecore.util.EContentsEList; -import org.eclipse.emf.ecore.util.EContentsEList.FeatureIterator; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.ui.IEditorPart; - -public class EMoflonModelAndMetamodelVisualiser extends EMoflonVisualiser { - private IEditorPart editor; - - @Override - protected String getDiagramBody(IEditorPart editor, ISelection selection) { - return maybeVisualiseMetamodel(editor)// - .orElse(maybeVisualiseModel(editor)// - .orElse(EMoflonPlantUMLGenerator.emptyDiagram()));// - } - - @SuppressWarnings("rawtypes") - private Optional multiSelectionInEcoreEditor(IEditorPart editor) { - return Optional.of(editor.getSite().getSelectionProvider())// - .flatMap(maybeCast(ISelectionProvider.class))// - .map(ISelectionProvider::getSelection)// - .flatMap(maybeCast(IStructuredSelection.class))// - .map(IStructuredSelection::toList);// - } - - private Optional maybeVisualiseMetamodel(IEditorPart editor) { - return extractMetamodelElementsFromEditor(editor) - .map(p -> EMoflonPlantUMLGenerator.visualiseEcoreElements(p.getLeft(), handleEOpposites(p.getRight()))); - } - - private Collection handleEOpposites(Collection refs) { - Collection refsWithOnlyOneEOpposite = new ArrayList<>(); - for (EReference eReference : refs) { - if (eReference.getEOpposite() == null || !refsWithOnlyOneEOpposite.contains(eReference.getEOpposite())) - refsWithOnlyOneEOpposite.add(eReference); - } - - return refsWithOnlyOneEOpposite; - } - - private Collection handleEOppositesForLinks(Collection links){ - Collection linksWithOnlyOneEOpposite = new ArrayList<>(); - for (VisualEdge link : links) { - if (!link.hasEOpposite() || !linksWithOnlyOneEOpposite.contains(link.findEOpposite(links).orElse(null))) - linksWithOnlyOneEOpposite.add(link); - } - - return linksWithOnlyOneEOpposite; - } - - private Optional maybeVisualiseModel(IEditorPart editor) { - return extractModelElementsFromEditor(editor).map(p -> { - if (checkForCorrespondenceModel(p.getLeft())) { - return EMoflonPlantUMLGenerator.visualiseCorrModel(p.getLeft(), - sourceTargetObjectsForCorrespondenceModel(p.getLeft(), "source"), - sourceTargetObjectsForCorrespondenceModel(p.getLeft(), "target"), - determineLinksToVisualizeForCorrModel(p.getLeft())); - } else { - return EMoflonPlantUMLGenerator.visualiseModelElements(p.getLeft(), p.getRight()); - } - }); - } - - private Optional, Collection>> extractModelElementsFromEditor( - IEditorPart editor2) { - return Optional.of(editor)// - .flatMap(this::multiSelectionInEcoreEditor)// - .map(this::determineObjectsAndLinksToVisualise)// - .map(p -> p.getLeft().isEmpty() ? null : p);// - } - - private Optional, Collection>> extractMetamodelElementsFromEditor( - IEditorPart editor) { - return Optional.of(editor)// - .flatMap(this::multiSelectionInEcoreEditor)// - .map(this::determineClassesAndRefsToVisualise)// - .map(p -> p.getLeft().isEmpty() ? null : p);// - } - - private Pair, Collection> determineClassesAndRefsToVisualise( - Collection selection) { - Collection chosenClassesfromResource = new ArrayList(); - if (selection.size() == 1 && !resourceChosen(selection).isEmpty()) { - TreeIterator eAllContents = resourceChosen(selection).get(0).getAllContents(); - while (eAllContents.hasNext()) { - EObject next = eAllContents.next(); - if (next instanceof EClass) { - chosenClassesfromResource.add((EClass) next); - } - } - - return Pair.of(chosenClassesfromResource, determineReferencesToVisualize(chosenClassesfromResource)); - } - - else { - Collection chosenClasses = selection.stream()// - .filter(EClass.class::isInstance)// - .map(EClass.class::cast)// - .collect(Collectors.toSet());// - - return Pair.of(chosenClasses, determineReferencesToVisualize(chosenClasses)); - } - } - - private Pair, Collection> determineObjectsAndLinksToVisualise( - Collection selection) { - Collection chosenObjectsfromResource = new ArrayList(); - if (selection.size() == 1 && !resourceChosen(selection).isEmpty()) { - TreeIterator eAllContents = resourceChosen(selection).get(0).getAllContents(); - while (eAllContents.hasNext()) { - EObject next = eAllContents.next(); - if (next instanceof EObject) - chosenObjectsfromResource.add(next); - } - - return Pair.of(chosenObjectsfromResource, determineLinksToVisualize(chosenObjectsfromResource)); - } - - else { - Collection chosenObjects = selection.stream()// - .filter(EObject.class::isInstance)// - .map(EObject.class::cast)// - .collect(Collectors.toSet());// - - return Pair.of(chosenObjects, determineLinksToVisualize(chosenObjects)); - } - } - - private Collection determineReferencesToVisualize(Collection chosenClasses) { - Collection refs = chosenClasses.stream()// - .flatMap(c -> c.getEReferences().stream())// - .collect(Collectors.toSet());// - return refs; - } - - @SuppressWarnings("rawtypes") - private Collection determineLinksToVisualize(Collection chosenObjects) { - Collection links = new HashSet<>(); - for (EObject o : new ArrayList(chosenObjects)) { - for (EContentsEList.FeatureIterator featureIterator = // - (EContentsEList.FeatureIterator) o.eCrossReferences().iterator(); featureIterator.hasNext();) { - addVisualEdge(featureIterator, chosenObjects, links, o); - } - for (EContentsEList.FeatureIterator featureIterator = // - (EContentsEList.FeatureIterator) o.eContents().iterator(); featureIterator.hasNext();) { - addVisualEdge(featureIterator, chosenObjects, links, o); - } - } - - return handleEOppositesForLinks(links); - } - - @SuppressWarnings("rawtypes") - private void addVisualEdge(FeatureIterator featureIterator, Collection chosenObjects, - Collection refs, EObject src) { - EObject trg = (EObject) featureIterator.next(); - EReference eReference = (EReference) featureIterator.feature(); - if (chosenObjects.contains(trg)) - refs.add(new VisualEdge(eReference, src, trg)); - } - - private List resourceChosen(Collection selection) { - List resourceChosen = selection.stream()// - .filter(Resource.class::isInstance)// - .map(Resource.class::cast)// - .collect(Collectors.toList());// - return resourceChosen; - - } - - private boolean checkForCorrespondenceModel(Collection chosenObjectsfromResource) { - // We do not expect any links to be present - if (!determineLinksToVisualize(chosenObjectsfromResource).isEmpty()) - return false; - - // We expect all objects to be of the form <--src--corr--trg--> - Iterator eAllContents = chosenObjectsfromResource.iterator(); - while (eAllContents.hasNext()) { - EObject next = eAllContents.next(); - if (!objectIsACorrespondenceLink(next)) - return false; - } - - return true; - } - - private boolean objectIsACorrespondenceLink(EObject next) { - EStructuralFeature refSrc = next.eClass().getEStructuralFeature("source"); - EStructuralFeature refTrg = next.eClass().getEStructuralFeature("target"); - - if(refSrc != null && refTrg != null) { - if(refSrc instanceof EReference && refTrg instanceof EReference) { - EObject src = (EObject) next.eGet(refSrc); - EObject trg = (EObject) next.eGet(refTrg); - - if(src != null && trg != null) - return true; - } - } - - return false; - } - - private Collection sourceTargetObjectsForCorrespondenceModel(Collection chosenObjectsfromResource, - String sourceOrTarget) { - Collection sourceOrTargetObjects = new ArrayList(); - Iterator eAllContents = chosenObjectsfromResource.iterator(); - while (eAllContents.hasNext()) { - EObject next = eAllContents.next(); - sourceOrTargetObjects.add((EObject) next.eGet(next.eClass().getEStructuralFeature(sourceOrTarget))); - } - - return sourceOrTargetObjects; - } - - private Collection determineLinksToVisualizeForCorrModel( - Collection chosenObjectsfromResource) { - Collection correspondenceObjects = new ArrayList(); - correspondenceObjects.addAll(sourceTargetObjectsForCorrespondenceModel(chosenObjectsfromResource, "source")); - correspondenceObjects.addAll(sourceTargetObjectsForCorrespondenceModel(chosenObjectsfromResource, "target")); - return determineLinksToVisualize(correspondenceObjects); - } - - @Override - public boolean supportsEditor(IEditorPart editor) { - this.editor = editor; - return extractMetamodelElementsFromEditor(editor).isPresent() - || extractModelElementsFromEditor(editor).isPresent(); - } - - @Override - public boolean supportsSelection(ISelection selection) { - return true; - } -} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonModelVisualiser.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonModelVisualiser.java new file mode 100644 index 00000000..ec1e5b1d --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonModelVisualiser.java @@ -0,0 +1,108 @@ +package org.moflon.core.ui.visualisation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.moflon.core.ui.VisualiserUtilities; +import org.moflon.core.ui.visualisation.Configurator.StrategyPart; +import org.moflon.core.ui.visualisation.strategy.DiagramStrategy; +import org.moflon.core.ui.visualisation.strategy.ObjectDiagramStrategies; + +/** + * Visualises UML Object Diagrams for Ecore models. + * + */ +public class EMoflonModelVisualiser extends EMoflonEcoreVisualiser { + + public EMoflonModelVisualiser() { + super(); + + // set default strategy + strategy = getDefaultStrategy(StrategyPart.INIT)// + .andThen(getDefaultStrategy(StrategyPart.NEIGHBOURHOOD)); + } + + @Override + public boolean supportsSelection(Collection selection) { + // An Ecore model must contain EObjects only, which are not EModelElements. If + // it contains other + // elements, the selection is not supported by this visualiser. + return !VisualiserUtilities.hasMetamodelElements(selection); + } + + @Override + public boolean supportsDiagramType(Class diagramClass) { + return ObjectDiagram.class == diagramClass; + } + + @Override + public DiagramStrategy getDefaultStrategy(StrategyPart part) { + switch (part) { + case INIT: + return ObjectDiagramStrategies::determineEdgesForSelection; + case NEIGHBOURHOOD: + return ObjectDiagramStrategies::expandNeighbourhoodBidirectional; + default: + return super.getDefaultStrategy(part); + } + } + + @Override + protected String getDiagramBody(Collection selection) { + Collection allObjects = getAllElements(); + + // Create diagram and process it using the defined strategy. + ObjectDiagram diagram = strategy.apply(new ObjectDiagram(allObjects, selection)); + diagram.setEdges(handleOpposites(diagram.getEdges())); + + diagram = determineObjectNames(diagram); + return EMoflonPlantUMLGenerator.visualiseModelElements(diagram, style); + } + + /** + * Determines instance names for all EObjects in selection and neighbourhood + * collection in the specified diagram. + * + * @param diagram + * The diagram, for which the EObject instance names shall be + * determined. + * @return The diagram with the EObject instance names. + */ + private ObjectDiagram determineObjectNames(ObjectDiagram diagram) { + int noEClassCount = 1; + Map instanceNames = diagram.getInstanceNames(); + Map instanceCounts = new HashMap<>(); + + determineObjectNames(diagram.getSelection(), instanceNames, instanceCounts, noEClassCount); + determineObjectNames(diagram.getNeighbourhood(), instanceNames, instanceCounts, noEClassCount); + + return diagram; + } + + private void determineObjectNames(Collection elements, Map instanceNames, + Map instanceCounts, int noEClassCount) { + for(EObject current : elements) { + // use EClass name with lower case first letter, if no EClass: "o" + String name = (current.eClass() != null) ? current.eClass().getName() : "o"; + name = (name == null || name.length() == 0) ? "o" : name; + name = name.substring(0, 1).toLowerCase() + name.substring(1); + + // no EClass -> use global object counter + if (current.eClass() == null) { + instanceNames.put(current, name + noEClassCount); + noEClassCount++; + continue; + } + + // determine and update instance count + int instanceCount = 1; + if (instanceCounts.containsKey(current.eClass())) { + instanceCount = instanceCounts.get(current.eClass()) + 1; + } + instanceNames.put(current, name + instanceCount); + instanceCounts.put(current.eClass(), instanceCount); + } + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonPlantUMLGenerator.xtend b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonPlantUMLGenerator.xtend index 82ab19a7..381f69ab 100644 --- a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonPlantUMLGenerator.xtend +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonPlantUMLGenerator.xtend @@ -2,13 +2,28 @@ package org.moflon.core.ui.visualisation import java.util.Collection import java.util.HashMap +import java.util.Map import org.apache.commons.lang3.StringUtils import org.eclipse.emf.ecore.EClass +import org.eclipse.emf.ecore.ENamedElement import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.EOperation import org.eclipse.emf.ecore.EReference +import org.eclipse.emf.ecore.EAnnotation +import org.eclipse.emf.ecore.EPackage +import java.util.Optional class EMoflonPlantUMLGenerator { + + public static final int SHOW_MODEL_DETAILS = 1<<0; + public static final int ABBR_LABELS = 1<<1; + public static final int SHOW_DOCUMENTATION = 1<<2; + + private static final String REPL_STR = "…"; + private static final int REPL_LEN = 11; + static var idMap = new HashMap(); + private static Map instanceNames; static def String wrapInTags(String body){ ''' @@ -43,40 +58,90 @@ class EMoflonPlantUMLGenerator { ''' } - def static String visualiseEcoreElements(Collection eclasses, Collection refs){ - ''' - «FOR c : eclasses» - «IF(c.abstract)»abstract «ENDIF»class «identifierForClass(c)» - «FOR s : c.ESuperTypes» - «IF(s.abstract)»abstract «ENDIF»class «identifierForClass(s)» - «identifierForClass(s)»<|--«identifierForClass(c)» - «ENDFOR» + def static String visualiseEcoreElements(ClassDiagram diagram, int diagramStyle){ + ''' + «plantUMLPreamble(diagramStyle)» + «FOR c : diagram.getSelection» + «IF(c.abstract)»abstract «ENDIF»class «identifierForClass(c, diagramStyle)» as «identifierForClass(c)» + «visualiseEcoreClassAttributes(c, diagramStyle)» + «visualiseEcoreClassOperations(c, diagramStyle)» + «ENDFOR» + «FOR c : diagram.getNeighbourhood» + «IF(c.abstract)»abstract «ENDIF»class «identifierForClass(c, diagramStyle)» as «identifierForClass(c)» + «visualiseEcoreClassAttributes(c, diagramStyle)» + «visualiseEcoreClassOperations(c, diagramStyle)» + «ENDFOR» + «visualiseEdges(diagram.getEdges, diagramStyle)» + «IF diagramStyle.bitwiseAnd(SHOW_DOCUMENTATION) > 0» + «visualiseDocumentation(diagram.getDoumentation)» + «ENDIF» + ''' + } + + def private static visualiseDocumentation(Map> map) { + ''' + «FOR a : map.keySet» + «var d = a.details.get("documentation")» + note "«identifierForAnnotation(d)»" as «aliasForDoc(a, d)» «ENDFOR» - «FOR r : refs» - «IF(r.EOpposite === null)» - «identifierForClass(r.EContainingClass)»«IF r.isContainment» *«ENDIF»--> "«multiplicityFor(r)»" «identifierForClass(r.EReferenceType)» : "«r.name»" - «ELSE» - «identifierForClass(r.EContainingClass)»"«r.EOpposite.name» «multiplicityFor(r.EOpposite)»" «IF r.isContainment»*«ELSE»<«ENDIF»--«IF r.EOpposite.isContainment»*«ELSE»>«ENDIF» "«r.name» «multiplicityFor(r)»" «identifierForClass(r.EReferenceType)» + «FOR a : map.keySet» + «var optC = map.get(a)» + «IF optC.isPresent» + «var d = a.details.get("documentation")» + «aliasForDoc(a, d)» .. «identifierForClass(optC.get)» «ENDIF» - «IF(r.EReferenceType.abstract)»abstract «ENDIF»class «identifierForClass(r.EReferenceType)» «ENDFOR» ''' } - def static String visualiseModelElements(Collection objects, Collection links){ + def private static aliasForDoc(EAnnotation a, String doc) { + '''«IF packageNameFor(a) !== ""»«packageNameFor(a)».«ENDIF»«doc.replaceAll("[\\W]", "_")»«a.hashCode()»''' + } + + def private static identifierForAnnotation(String d) { + '''«d.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "'")»''' + } + + def static String visualiseModelElements(ObjectDiagram diagram, int diagramStyle){ idMap.clear + instanceNames = diagram.eObjectsToNames; ''' - «FOR o : objects» - object «identifierForObject(o)»{ - «visualiseAllAttributes(o)» + «plantUMLPreamble(diagramStyle)» + «FOR o : diagram.getSelection» + object «identifierForObject(o, diagramStyle)» as «identifierForObject(o)» { + «visualiseAllAttributes(o, diagramStyle)» } «ENDFOR» - «FOR l : links» - «IF(!l.hasEOpposite)» - «identifierForObject(l.src)» --> «identifierForObject(l.trg)» : "«l.name»" - «ELSE» - «identifierForObject(l.src)» "«l.oppositeName»" <--> "«l.name»" «identifierForObject(l.trg)» + «FOR o : diagram.getNeighbourhood» + object «identifierForObject(o, diagramStyle)» as «identifierForObject(o)» { + «visualiseAllAttributes(o, diagramStyle)» + } + «ENDFOR» + «visualiseEdges(diagram.getEdges, diagramStyle)» + ''' + } + + def private static String visualiseEdges(Collection edges, int style) { + ''' + «FOR edge: edges» + «IF(edge.edgeType == EdgeType.REFERENCE)» + «var EReference ref = edge.type» + «var EClass src = ref.EContainingClass» + «var EClass trg = ref.EReferenceType» + «IF(!edge.hasEOpposite)» + «identifierForClass(src)»«IF ref.isContainment» *«ENDIF»--> "«multiplicityFor(ref)»" «identifierForClass(trg)» : "«nameFor(ref, style)»" + «ELSE» + «identifierForClass(src)»"«nameFor(ref.EOpposite, style)» «multiplicityFor(ref.EOpposite)»" «IF ref.isContainment»*«ELSE»<«ENDIF»--«IF ref.EOpposite.isContainment»*«ELSE»>«ENDIF» "«nameFor(ref, style)» «multiplicityFor(ref)»" «identifierForClass(trg)» + «ENDIF» + «ELSEIF(edge.edgeType == EdgeType.GENERALISATION)» + «identifierForClass(edge.trg as EClass)»<|--«identifierForClass(edge.src as EClass)» + «ELSEIF(edge.edgeType == EdgeType.LINK)» + «IF(!edge.hasEOpposite)» + «identifierForObject(edge.src)» --> «identifierForObject(edge.trg)» : "«IF style.bitwiseAnd(ABBR_LABELS) > 0»«abbr(edge.name)»«ELSE»«edge.name»«ENDIF»" + «ELSE» + «identifierForObject(edge.src)» "«IF style.bitwiseAnd(ABBR_LABELS) > 0»«abbr(edge.oppositeName)»«ELSE»«edge.oppositeName»«ENDIF»" <--> "«IF style.bitwiseAnd(ABBR_LABELS) > 0»«abbr(edge.name)»«ELSE»«edge.name»«ENDIF»" «identifierForObject(edge.trg)» + «ENDIF» «ENDIF» «ENDFOR» ''' @@ -86,8 +151,43 @@ class EMoflonPlantUMLGenerator { '''«IF r.lowerBound == -1»*«ELSE»«r.lowerBound»«ENDIF»..«IF r.upperBound == -1»*«ELSE»«r.upperBound»«ENDIF»''' } + private def static String identifierForClass(EClass c, int style) + '''"«nameFor(c, style)»"''' + private def static String identifierForClass(EClass c) - '''"«c.EPackage.name».«c.name»"''' + '''«nameFor(c.EPackage)».«nameFor(c)»''' + + private def static String visualiseEcoreClassAttributes(EClass eclass, int style) { + ''' + «FOR a : eclass.EAllAttributes» + «identifierForClass(eclass)» : «nameFor(a, style)» : «nameFor(a.EType, style)» + «ENDFOR» + ''' + } + + private def static String visualiseEcoreClassOperations(EClass eclass, int style) { + ''' + «FOR op : eclass.EAllOperations» + «identifierForClass(eclass)» : «visualiseEcoreOperation(op, style)» + «ENDFOR» + ''' + } + + private def static String visualiseEcoreOperation(EOperation op, int style) { + '''«nameFor(op, style)»«visualiseEcoreOperationParameterList(op, style)»«IF(op.EType !== null)» : «nameFor(op.EType, style)»«ENDIF»''' + } + + private def static String visualiseEcoreOperationParameterList(EOperation op, int style) { + '''«IF op.EParameters.size == 0»()«ENDIF»«FOR param : op.EParameters BEFORE '(' SEPARATOR ', ' AFTER ')'»«nameFor(param, style)» : «nameFor(param.EType, style)»«ENDFOR»''' + } + + def static visualiseAllAttributes(EObject o, int style) { + ''' + «FOR a : o.eClass.EAllAttributes» + «nameFor(a, style)» = «IF o.eGet(a) !== null && style.bitwiseAnd(ABBR_LABELS) > 0»«abbr(o.eGet(a).toString)»«ELSE»«o.eGet(a)»«ENDIF» + «ENDFOR» + ''' + } def static visualiseAllAttributes(EObject o) { ''' @@ -97,11 +197,12 @@ class EMoflonPlantUMLGenerator { ''' } + private def static Object identifierForObject(EObject o, int style){ + '''"«IF style.bitwiseAnd(ABBR_LABELS) > 0»«abbr(instanceNames.get(o))»«ELSE»«instanceNames.get(o)»«ENDIF» : «nameFor(o.eClass, style)»"''' + } + private def static Object identifierForObject(EObject o){ - if(!idMap.containsKey(o)) - idMap.put(o, '''o«idMap.keySet.size + 1»''') - - '''«idMap.get(o)».«o.eClass.name»''' + '''«instanceNames.get(o)».«nameFor(o.eClass)»''' } def static String visualiseCorrModel(Collection corrObjects, Collection sourceObjects, Collection targetObjects, Collection links) @@ -155,4 +256,56 @@ class EMoflonPlantUMLGenerator { } ''' } + + def static CharSequence plantUMLPreamble(int style){ + ''' + hide «IF(style.bitwiseAnd(SHOW_MODEL_DETAILS) > 0)»empty «ENDIF»members + + skinparam shadowing false + skinparam StereotypeABackgroundColor White + skinparam StereotypeCBackgroundColor White + + skinparam class { + BorderColor Black + BackgroundColor White + ArrowColor Black + StereotypeABackgroundColor White + StereotypeCBackgroundColor White + } + + skinparam package { + BackgroundColor GhostWhite + BorderColor LightSlateGray + Fontcolor LightSlateGray + } + + skinparam object { + BorderColor Black + BackgroundColor White + ArrowColor Black + } + + skinparam note { + BorderColor Black + BackgroundColor White + ArrowColor Black + } + ''' + } + + def private static String nameFor(ENamedElement elem, int style) { + '''«IF style.bitwiseAnd(ABBR_LABELS) > 0»«abbr(elem.name)»«ELSE»«elem.name»«ENDIF»''' + } + + def private static String nameFor(ENamedElement elem) { + '''«elem.name»''' + } + + def private static String packageNameFor(EObject obj) { + '''«IF obj instanceof EPackage»«nameFor(obj as EPackage)»«ELSE»«IF obj.eContainer !== null»«packageNameFor(obj.eContainer)»«ENDIF»«ENDIF»''' + } + + def private static String abbr(String longString) { + '''«StringUtils.abbreviateMiddle(longString, REPL_STR, REPL_LEN)»''' + } } diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonVisualiser.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonVisualiser.java index cd4fa6f0..b2f63c24 100644 --- a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonVisualiser.java +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EMoflonVisualiser.java @@ -15,6 +15,10 @@ public abstract class EMoflonVisualiser implements DiagramTextProvider { private static final Logger logger = Logger.getLogger(EMoflonVisualiser.class); private static final int MAX_SIZE = 500; + public EMoflonVisualiser() { + Configurator.getInstance().registerVisualiser(this); + } + @Override public String getDiagramText(IEditorPart editor, ISelection selection) { Optional diagram = Optional.empty(); diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EdgeType.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EdgeType.java new file mode 100644 index 00000000..7a14a4b9 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/EdgeType.java @@ -0,0 +1,16 @@ +package org.moflon.core.ui.visualisation; + +/** + * Used to distinguish classes of edges. + * + * {@link EdgeType#GENERALISATION} identifies generalisation dependencies in + * class diagrams, {@link EdgeType#REFERENCE} identifies references in class + * diagrams, and {@link EdgeType#LINK} is used to identify cross-references or + * containment-relations in object diagrams. + * + * @author Johannes Brandt (initial contribution) + * + */ +public enum EdgeType { + GENERALISATION, REFERENCE, LINK; +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ObjectDiagram.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ObjectDiagram.java new file mode 100644 index 00000000..fa6ecee0 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/ObjectDiagram.java @@ -0,0 +1,57 @@ +/** + * + */ +package org.moflon.core.ui.visualisation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.ecore.EObject; + +/** + * Represents an object diagram. + * + * The nodes of this diagram type are {@link EObject} instances. + * + * @author Johannes Brandt + * + */ +public class ObjectDiagram extends Diagram { + + /** + * Provides an instance name for an EObject. + */ + protected Map eObjectsToNames; + + public ObjectDiagram(Collection superset) { + super(superset); + + eObjectsToNames = new HashMap<>(); + } + + public ObjectDiagram(Collection superset, Collection selection) { + super(superset, selection); + + eObjectsToNames = new HashMap<>(); + } + + /** + * Getter for {@link #eObjectsToNames}. + * + * @return The mapping of EObjects to their respective instance name. + */ + public Map getInstanceNames() { + return eObjectsToNames; + } + + /** + * Setter for {@link #eObjectsToNames}. + * + * @param eObjectsToNames + * The new mapping of EObjects to their respective instance name. + */ + public void setInstanceNames(Map eObjectsToNames) { + this.eObjectsToNames = eObjectsToNames; + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/VisualEdge.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/VisualEdge.java index 0439719b..b6900613 100644 --- a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/VisualEdge.java +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/VisualEdge.java @@ -8,11 +8,13 @@ public class VisualEdge { private EReference type; + private EdgeType edgeType; private EObject src; private EObject trg; - - public VisualEdge(EReference type, EObject src, EObject trg) { + + public VisualEdge(EReference type, EdgeType edgeType, EObject src, EObject trg) { this.type = type; + this.edgeType = edgeType; this.src = src; this.trg = trg; } @@ -21,6 +23,10 @@ public EReference getType() { return type; } + public EdgeType getEdgeType() { + return edgeType; + } + public EObject getSrc() { return src; } @@ -28,24 +34,30 @@ public EObject getSrc() { public EObject getTrg() { return trg; } - + public String getName() { + if (edgeType == EdgeType.GENERALISATION || type == null) { + return ""; + } return type.getName(); } - + public String getOppositeName() { - assert(hasEOpposite()); + assert (hasEOpposite()); return type.getEOpposite().getName(); } public boolean hasEOpposite() { + if (edgeType == EdgeType.GENERALISATION || type == null) { + return false; + } return type.getEOpposite() != null; } public Optional findEOpposite(Collection links) { - assert(hasEOpposite()); - VisualEdge opposite = new VisualEdge(type.getEOpposite(), trg, src); - if(links.contains(opposite)) + assert (hasEOpposite()); + VisualEdge opposite = new VisualEdge(type.getEOpposite(), edgeType, trg, src); + if (links.contains(opposite)) return Optional.of(opposite); else return Optional.empty(); @@ -58,6 +70,7 @@ public int hashCode() { result = prime * result + ((src == null) ? 0 : src.hashCode()); result = prime * result + ((trg == null) ? 0 : trg.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((edgeType == null) ? 0 : edgeType.hashCode()); return result; } @@ -65,7 +78,7 @@ public int hashCode() { public String toString() { return "--" + getName() + "-->"; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -75,6 +88,8 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; VisualEdge other = (VisualEdge) obj; + if (edgeType != other.edgeType) + return false; if (src == null) { if (other.src != null) return false; @@ -92,6 +107,4 @@ public boolean equals(Object obj) { return false; return true; } - - } diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/ClassDiagramStrategies.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/ClassDiagramStrategies.java new file mode 100644 index 00000000..22c61fbd --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/ClassDiagramStrategies.java @@ -0,0 +1,120 @@ +package org.moflon.core.ui.visualisation.strategy; + +import java.util.Collection; +import java.util.HashSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.emf.ecore.EClass; +import org.moflon.core.ui.visualisation.ClassDiagram; +import org.moflon.core.ui.visualisation.EdgeType; +import org.moflon.core.ui.visualisation.VisualEdge; + +/** + * Contains various methods for manipulating {@link ClassDiagram} instances. + * + * @author Johannes Brandt + * + */ +public class ClassDiagramStrategies { + + /** + * Computes all edges between selected classes in the given class diagram. + * + * This method will add each computed edge to the edges stored with the diagram. + * Only edges between selected classes will be computed and added. + * + * @param diagram + * The class diagram containing the selection, for which the edges + * are to be computed. + * @return The resulting class diagram. + */ + public static ClassDiagram determineEdgesForSelection(ClassDiagram diagram) { + Collection selection = diagram.getSelection(); + Collection edges = diagram.getEdges(); + + determineOutboundEdgesBetween(selection, selection, edges); + + return diagram; + } + + /** + * Expands the given {@link ClassDiagram}'s neighbourhood by one degree, + * bidirectional. + * + * The given diagram's neighbourhood is expanded, by adding all neighbors of the + * current neighbourhood. The direction of the associations between classes is + * irrelevant. If no neighbourhood is defined with the given diagram, then the + * selection's neighbours are added to the neighbourhood. + * + *

+ * Note: If a neighbourhood expansion of a degree greater than one is + * wished, this method can simple be chained. + *

+ * + * @param diagram + * The diagram, of which the neighbourhood is to be increased by a + * degree of one. + * @return The diagram with the increased neighbourhood degree. + */ + public static ClassDiagram expandNeighbourhoodBidirectional(ClassDiagram diagram) { + Collection selection = diagram.getSelection(); + Collection neighbourhood = diagram.getNeighbourhood(); + Collection others = diagram.getSuperset().stream()// + .filter(cls -> !selection.contains(cls))// + .filter(cls -> !neighbourhood.contains(cls))// + .collect(Collectors.toCollection(HashSet::new)); + Collection edges = diagram.getEdges(); + + // find 1-neighbourhood edges + if (neighbourhood.isEmpty()) { + determineOutboundEdgesBetween(selection, others, edges); + determineOutboundEdgesBetween(others, selection, edges); + } else { + determineOutboundEdgesBetween(neighbourhood, others, edges); + determineOutboundEdgesBetween(others, neighbourhood, edges); + } + + // update neighbourhood + edges.stream()// + .flatMap(edge -> Stream.of((EClass) edge.getSrc(), (EClass) edge.getTrg()))// + .filter(cls -> !selection.contains(cls))// + .filter(cls -> !neighbourhood.contains(cls))// + .forEach(neighbourhood::add); + + return diagram; + } + + /** + * Determines all outbound edges from classes in sourceElements to + * classes in targetElements. + * + * @param sourceElements + * The set of classes for which all outbound edges shall be + * determined. + * @param targetElements + * The set of classes which represent targets of all outbound edges + * from the set of source elements. + * @param edges + * All edges with a class from sourceElements as source, + * and a class from targetElements as target. + */ + private static void determineOutboundEdgesBetween(Collection sourceElements, + Collection targetElements, Collection edges) { + // search references + sourceElements.stream()// + .flatMap(cls -> cls.getEReferences().stream())// + .filter(ref -> targetElements.contains(ref.getEReferenceType()))// + .map(ref -> new VisualEdge(ref, EdgeType.REFERENCE, ref.getEContainingClass(), ref.getEReferenceType()))// + .forEach(edges::add);// + + // search generalisations + for (EClass c : sourceElements) { + for (EClass s : c.getESuperTypes()) { + if (targetElements.contains(s)) { + edges.add(new VisualEdge(null, EdgeType.GENERALISATION, c, s)); + } + } + } + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/DiagramStrategy.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/DiagramStrategy.java new file mode 100644 index 00000000..ad2ddee7 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/DiagramStrategy.java @@ -0,0 +1,29 @@ +/** + * + */ +package org.moflon.core.ui.visualisation.strategy; + +import java.util.Objects; +import java.util.function.UnaryOperator; + +/** + * @author Johannes Brandt + * + */ +@FunctionalInterface +public interface DiagramStrategy extends UnaryOperator { + + static DiagramStrategy identity() { + return arg -> arg; + } + + default DiagramStrategy andThen(DiagramStrategy after) { + Objects.requireNonNull(after); + return arg -> after.apply(this.apply(arg)); + } + + default DiagramStrategy compose(DiagramStrategy before) { + Objects.requireNonNull(before); + return arg -> this.apply(before.apply(arg)); + } +} diff --git a/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/ObjectDiagramStrategies.java b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/ObjectDiagramStrategies.java new file mode 100644 index 00000000..aca9d260 --- /dev/null +++ b/org.moflon.core.ui/src/org/moflon/core/ui/visualisation/strategy/ObjectDiagramStrategies.java @@ -0,0 +1,123 @@ +package org.moflon.core.ui.visualisation.strategy; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EContentsEList; +import org.moflon.core.ui.visualisation.EdgeType; +import org.moflon.core.ui.visualisation.ObjectDiagram; +import org.moflon.core.ui.visualisation.VisualEdge; + +/** + * Contains various methods for manipulating {@link ObjectDiagram} instances. + * + * @author Johannes Brandt + * + */ +public class ObjectDiagramStrategies { + + /** + * Computes all edges between selected objects in the given object diagram. + * + * This method will add each computed edge to the edges stored with the diagram. + * Only edges between selected objects will be computed and added. + * + * @param diagram + * The object diagram containing the selection, for which the edges + * are to be computed. + * @return The resulting object diagram. + */ + public static ObjectDiagram determineEdgesForSelection(ObjectDiagram diagram) { + Collection selection = diagram.getSelection(); + Collection edges = diagram.getEdges(); + + determineOutboundEdgesBetween(selection, selection, edges); + + return diagram; + } + + /** + * Expands the given {@link ObjectDiagram}'s neighbourhood by one degree, + * bidirectional. + * + * The given diagram's neighbourhood is expanded, by adding all neighbors of the + * current neighbourhood. The direction of the associations between objects is + * irrelevant. If no neighbourhood is defined with the given diagram, then the + * selection's neighbours are added to the neighbourhood. + * + *

+ * Note: If a neighbourhood expansion of a degree greater than one is + * wished, this method can simple be chained. + *

+ * + * @param diagram + * The diagram, of which the neighbourhood is to be increased by a + * degree of one. + * @return The diagram with the increased neighbourhood degree. + */ + public static ObjectDiagram expandNeighbourhoodBidirectional(ObjectDiagram diagram) { + Collection selection = diagram.getSelection(); + Collection neighbourhood = diagram.getNeighbourhood(); + Collection others = diagram.getSuperset().stream()// + .filter(obj -> !selection.contains(obj))// + .filter(obj -> !neighbourhood.contains(obj))// + .collect(Collectors.toCollection(HashSet::new)); + Collection edges = diagram.getEdges(); + + // find 1-neighbourhood edges + if (neighbourhood.isEmpty()) { + determineOutboundEdgesBetween(selection, others, edges); + determineOutboundEdgesBetween(others, selection, edges); + } else { + determineOutboundEdgesBetween(neighbourhood, others, edges); + determineOutboundEdgesBetween(others, neighbourhood, edges); + } + + // update neighbourhood + edges.stream()// + .flatMap(edge -> Stream.of(edge.getSrc(), edge.getTrg()))// + .filter(obj -> !selection.contains(obj))// + .filter(obj -> !neighbourhood.contains(obj))// + .forEach(neighbourhood::add); + + return diagram; + } + + /** + * Determines all outbound edges from objects in sourceElements to + * objects in targetElements. + * + * @param sourceElements + * The set of objects for which all outbound edges shall be + * determined. + * @param targetElements + * The set of objects which represent targets of all outbound edges + * from the set of source elements. + * @param edges + * All edges with an object from sourceElements as + * source, and an object from targetElements as target. + */ + private static void determineOutboundEdgesBetween(Collection sourceElements, + Collection targetElements, Collection edges) { + for (EObject obj : sourceElements) { + final EList eCrossReferences = obj.eCrossReferences(); + final EList eContents = obj.eContents(); + for (final EList featureList : Arrays.asList(eCrossReferences, eContents)) { + for (final EContentsEList.FeatureIterator featureIterator = // + (EContentsEList.FeatureIterator) featureList.iterator(); // + featureIterator.hasNext();) { + EObject trg = (EObject) featureIterator.next(); + EReference eReference = (EReference) featureIterator.feature(); + if (targetElements.contains(trg)) + edges.add(new VisualEdge(eReference, EdgeType.LINK, obj, trg)); + } + } + } + } +} diff --git a/org.moflon.core.utilities/META-INF/MANIFEST.MF b/org.moflon.core.utilities/META-INF/MANIFEST.MF index 0456326b..b29606a7 100644 --- a/org.moflon.core.utilities/META-INF/MANIFEST.MF +++ b/org.moflon.core.utilities/META-INF/MANIFEST.MF @@ -57,6 +57,7 @@ Require-Bundle: org.apache.commons.io;bundle-version="[2.0.1,3.0.0)";visibility: org.eclipse.ui, org.eclipse.emf.codegen.ecore, org.eclipse.pde.core, - org.eclipse.emf.mwe.core + org.eclipse.emf.mwe.core, + org.gervarro.eclipse.workspace.util Bundle-ActivationPolicy: lazy Automatic-Module-Name: org.moflon.core.utilities diff --git a/org.moflon.core.utilities/src/org/moflon/core/utilities/ExceptionUtil.java b/org.moflon.core.utilities/src/org/moflon/core/utilities/ExceptionUtil.java index a82dd456..79ea7dd2 100644 --- a/org.moflon.core.utilities/src/org/moflon/core/utilities/ExceptionUtil.java +++ b/org.moflon.core.utilities/src/org/moflon/core/utilities/ExceptionUtil.java @@ -18,6 +18,16 @@ private ExceptionUtil() { throw new UtilityClassNotInstantiableException(); } + /** + * Creates an {@link IStatus} that reports the given {@link Throwable}. + * @param reportingClass the class that reports the error (used for determining the plugin id) + * @param t the error to report + * @return the resulting {@link IStatus} having flag {@link IStatus#ERROR} and the message of the {@link Throwable} as status message + */ + public static IStatus createDefaultErrorStatus(final Class reportingClass, final Throwable t) { + return new Status(IStatus.ERROR, WorkspaceHelper.getPluginId(reportingClass), t.getMessage(), t); + } + /** * Formats the given exception for debugging purposes. * @@ -46,7 +56,7 @@ public static String displayExceptionAsString(final Exception e) { /** * Throws a {@link CoreException} that contains an {@link IStatus} with the * given message, plugin ID, and wrapped exception - * + * * @param message * error message * @param plugin diff --git a/org.moflon.core.utilities/src/org/moflon/core/utilities/MoflonUtil.java b/org.moflon.core.utilities/src/org/moflon/core/utilities/MoflonUtil.java index 10c50f2e..69f73706 100644 --- a/org.moflon.core.utilities/src/org/moflon/core/utilities/MoflonUtil.java +++ b/org.moflon.core.utilities/src/org/moflon/core/utilities/MoflonUtil.java @@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EcorePackage; @@ -44,7 +45,12 @@ public static String eCoreTypeToJavaType(final String eCoreType) throws IllegalA // Derive the java data type from the Ecore class name try { - javaType = EcorePackage.eINSTANCE.getEClassifier(eCoreType).getInstanceClass().getSimpleName(); + final EClassifier eClassifier = EcorePackage.eINSTANCE.getEClassifier(eCoreType); + if (eClassifier != null) { + javaType = eClassifier.getInstanceClass().getSimpleName(); + } else { + javaType = eCoreType; + } } catch (Exception e) { logger.debug("Cannot derive Java data type from the given Ecore data type = '" + eCoreType + "'. Using Ecore type instead."); @@ -86,7 +92,7 @@ public static String handlePrefixForBooleanAttributes(final String packageName, * Returns the last segment of a fully-qualified name * * Example: For the name 'x.y.z.A', the last segment is 'A' - * + * * @param name * the fully-qualified name * @return the last segment of the name @@ -104,7 +110,7 @@ public static String lastSegmentOf(final String name) { /** * Returns the capitalized last segment of the given fully-qualified name - * + * * @param name * the name * @return the result of capitalizing {@link #lastSegmentOf(String)} @@ -117,7 +123,7 @@ public static String lastCapitalizedSegmentOf(final String name) { * Returns all segments but the last one of the given fully-qualified name * * Example: For the name 'x.y.z.A', the result is 'x.y.z' - * + * * @param name * the fully-qualified name * @return all but the last segment @@ -156,7 +162,7 @@ public static String transformPackageNameUsingImportMapping(final String fullyQu /** * Joins the given list of segments using '.', resulting in a qualified name - * + * * @param segments * the segments * @return the qualified name diff --git a/org.moflon.core.utilities/src/org/moflon/core/utilities/WorkspaceHelper.java b/org.moflon.core.utilities/src/org/moflon/core/utilities/WorkspaceHelper.java index 7aa4cd50..3420de7a 100644 --- a/org.moflon.core.utilities/src/org/moflon/core/utilities/WorkspaceHelper.java +++ b/org.moflon.core.utilities/src/org/moflon/core/utilities/WorkspaceHelper.java @@ -515,6 +515,10 @@ public static boolean isFile(final IResource resource) { public static boolean isEcoreFile(final IResource resource) { return isFile(resource) && resource.getName().endsWith(".ecore"); } + + public static boolean isXcoreFile(final IResource resource) { + return isFile(resource) && resource.getName().endsWith(".xcore"); + } /** * Returns the {@link IProject} with the given name (if exists) diff --git a/org.moflon.core.utilities/src/org/moflon/core/utilities/eMoflonEMFUtil.java b/org.moflon.core.utilities/src/org/moflon/core/utilities/eMoflonEMFUtil.java index 539caa68..18cce04b 100644 --- a/org.moflon.core.utilities/src/org/moflon/core/utilities/eMoflonEMFUtil.java +++ b/org.moflon.core.utilities/src/org/moflon/core/utilities/eMoflonEMFUtil.java @@ -1050,8 +1050,13 @@ private static ECrossReferenceAdapter getCRAdapter(final EObject target) { * @param pluginModel * the plugin model * @return the symbolic name + * @throws Exception if the symbolic name cannot be extracted from the model */ private static String extractSymbolicName(final IPluginModelBase pluginModel) { + if (pluginModel == null || pluginModel.getBundleDescription() == null) { + throw new IllegalArgumentException("Cannot extract symbolic name from bundle."); + } + return pluginModel.getBundleDescription().getSymbolicName(); } diff --git a/org.moflon.core.xtext/META-INF/MANIFEST.MF b/org.moflon.core.xtext/META-INF/MANIFEST.MF index 44e41b5b..e804978d 100644 --- a/org.moflon.core.xtext/META-INF/MANIFEST.MF +++ b/org.moflon.core.xtext/META-INF/MANIFEST.MF @@ -2,12 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: org.moflon.core.xtext Bundle-SymbolicName: org.moflon.core.xtext;singleton:=true -Bundle-Version: 1.0.0.qualifier +Bundle-Version: 2.0.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Require-Bundle: org.eclipse.xtext.ui +Require-Bundle: org.eclipse.xtext.ui, + org.moflon.core.utilities;bundle-version="3.0.0" Export-Package: org.moflon.core.xtext.exceptions, org.moflon.core.xtext.scoping, - org.moflon.core.xtext.scoping.utils, - org.moflon.core.xtext.utils + org.moflon.core.xtext.scoping.utils Bundle-Vendor: eMoflon Developers Automatic-Module-Name: org.moflon.core.xtext diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/ScopeProviderHelper.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/ScopeProviderHelper.java index a5445dcd..885b2855 100644 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/ScopeProviderHelper.java +++ b/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/ScopeProviderHelper.java @@ -15,7 +15,7 @@ import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.scoping.IScope; import org.moflon.core.xtext.exceptions.CannotFindScopeException; -import org.moflon.core.xtext.scoping.utils.MOSLScopeUtil; +import org.moflon.core.xtext.utils.ResourceUtil; public class ScopeProviderHelper { private Map existingScopingRoots; @@ -29,7 +29,7 @@ public ScopeProviderHelper(ResourceSet resSet) { public ScopeProviderHelper() { init(); - resourceSet = MOSLScopeUtil.getInstance().getResourceSet("ecore"); + resourceSet = ResourceUtil.getResourceSet("ecore"); } private void init(){ diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/MOSLScopeUtil.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/MOSLScopeUtil.java index d690dacf..fbc4d0cd 100644 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/MOSLScopeUtil.java +++ b/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/MOSLScopeUtil.java @@ -4,112 +4,52 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Stack; import java.util.stream.Collectors; import org.eclipse.emf.common.util.URI; 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.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; - -public class MOSLScopeUtil -{ - private static MOSLScopeUtil instance; - - private MOSLScopeUtil() - { - } - - public static MOSLScopeUtil getInstance() - { - if (instance == null) - instance = new MOSLScopeUtil(); - return instance; - } - - public R getRootObject(EObject context, Class clazz) - { - Stack stack = new Stack(); - stack.push(context); - while (!stack.isEmpty()) - { - EObject element = stack.pop(); - if (element == null) - { - return null; - } else if (clazz.isInstance(element)) - return clazz.cast(element); - stack.push(element.eContainer()); - } - return null; - } - - public List getObjectsFromResource(Resource resource, Class clazz){ - List allContent = new ArrayList<>(); - resource.getAllContents().forEachRemaining(allContent::add); - return allContent.parallelStream().filter(clazz::isInstance).map(clazz::cast).collect(Collectors.toList()); - } - - public E getObjectFromResourceSet(URI uri, ResourceSet resourceSet, Class clazz) - { - Resource res = getResource(uri, resourceSet, true); - E scopingRoot = clazz.cast(res.getContents().get(0)); - return scopingRoot; - } - - public ResourceSet getResourceSet() - { - return getResourceSet("xmi"); - } - - private Resource getResource(URI uri, ResourceSet resourceSet, boolean load) - { - try - { - Resource res = resourceSet.getResource(uri, false); - if (res == null) - { - res = resourceSet.createResource(uri); - } - if (load) - res.load(Collections.EMPTY_MAP); - return res; - } catch (IOException e) - { - throw new RuntimeException(e); - } - } - - public Resource addToResource(URI uri, ResourceSet resourceSet, EObject obj) - { - Resource resource = getResource(uri, resourceSet, false); - resource.getContents().clear(); - resource.getContents().add(obj); - return resource; - } - - public void saveToResource(URI uri, ResourceSet resourceSet, EObject obj) - { - try - { - Resource resource = addToResource(uri, resourceSet, obj); - resource.save(Collections.EMPTY_MAP); - } catch (IOException e) - { - throw new RuntimeException(e); - } - } - - public ResourceSet getResourceSet(String ext) - { - ResourceSet resourceSet = new ResourceSetImpl(); - Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE; - Map m = reg.getExtensionToFactoryMap(); - Object factory = m.getOrDefault(ext, new XMIResourceFactoryImpl()); - m.put(ext, factory); - return resourceSet; - } +import org.moflon.core.xtext.utils.ResourceUtil; + +public class MOSLScopeUtil { + @Deprecated // Since 2018-08-09 + private static MOSLScopeUtil instance; + + /** + * @deprecated All methods are now static, so we do not need the synthetic + * singleton + */ + @Deprecated // Since 2018-08-09 + public static MOSLScopeUtil getInstance() { + if (instance == null) + instance = new MOSLScopeUtil(); + return instance; + } + + public static List getObjectsFromResource(Resource resource, Class clazz) { + List allContent = new ArrayList<>(); + resource.getAllContents().forEachRemaining(allContent::add); + return allContent.parallelStream().filter(clazz::isInstance).map(clazz::cast).collect(Collectors.toList()); + } + + public static ResourceSet getResourceSet() { + return ResourceUtil.getResourceSet("xmi"); + } + + public static Resource addToResource(URI uri, ResourceSet resourceSet, EObject obj) { + Resource resource = ResourceUtil.getResource(uri, resourceSet, false); + resource.getContents().clear(); + resource.getContents().add(obj); + return resource; + } + + public static void saveToResource(URI uri, ResourceSet resourceSet, EObject obj) { + try { + Resource resource = addToResource(uri, resourceSet, obj); + resource.save(Collections.EMPTY_MAP); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/ScopeProviderHelper.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/ScopeProviderHelper.java index cf2bc856..93c84860 100644 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/ScopeProviderHelper.java +++ b/org.moflon.core.xtext/src/org/moflon/core/xtext/scoping/utils/ScopeProviderHelper.java @@ -17,33 +17,30 @@ import org.moflon.core.xtext.exceptions.CannotFindScopeException; import org.moflon.core.xtext.utils.ResourceUtil; - - - public class ScopeProviderHelper { private Map existingScopingRoots; private Map> oldCandidates; private ResourceSet resourceSet; - + public ScopeProviderHelper(ResourceSet resSet) { init(); resourceSet = resSet; } - + public ScopeProviderHelper() { init(); - resourceSet = ResourceUtil.getInstance().getResourceSet("ecore"); + resourceSet = ResourceUtil.getResourceSet("ecore"); } - + private void init(){ existingScopingRoots = new HashMap<>(); oldCandidates = new HashMap<>(); } - + public ResourceSet getResourceSet(){ return resourceSet; } - + private E getScopingObject(URI uri, Class clazz) throws IOException{ if(existingScopingRoots.containsKey(uri)){ return existingScopingRoots.get(uri); @@ -57,21 +54,21 @@ private E getScopingObject(URI uri, Class clazz) throws IOException{ existingScopingRoots.put(uri, scopingRoot); return scopingRoot; } - } - + } + public IScope createScope(List uris, Class clazz, Class type) throws CannotFindScopeException{ return createScope(uris, clazz, type, null); - } - + } + public IScope createScope(List uris, Class clazz, Class type, List currentFound) throws CannotFindScopeException{ try { List candidates=null; if(oldCandidates.containsKey(type.toGenericString())){ candidates=oldCandidates.get(type.toGenericString()); } - else { + else { candidates = new ArrayList<>(); - + for(URI uri : uris){ E scopingObject=getScopingObject(uri, clazz); Iterator candidateIterator = scopingObject.eAllContents(); @@ -90,5 +87,5 @@ public IScope createScope(List uris, Class clazz, Cl }catch (Exception ioobe){ throw new CannotFindScopeException("Cannot find Resource"); } - } + } } diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/BiMap.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/BiMap.java deleted file mode 100644 index fed4fe52..00000000 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/BiMap.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.moflon.core.xtext.utils; - -import java.util.Map; - -public interface BiMap extends Map{ - V getValue(K key); - K getKey(V value); - - V getValueOrDefault(K key, V defaultValue); - K getKeyOrDefault(V value, K defaultKey); - - V removeKey(K key); - K removeValue(V value); -} diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/HashBiMap.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/HashBiMap.java deleted file mode 100644 index 90db3c50..00000000 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/HashBiMap.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.moflon.core.xtext.utils; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class HashBiMap implements BiMap{ - - private Map map = new HashMap(); - private Map inversedMap = new HashMap(); - - public V put(K k, V v) { - map.put(k, v); - inversedMap.put(v, k); - return v; - } - - @Override - public V get(Object k) { - return map.get(k); - } - - public K getKey(V v) { - return inversedMap.get(v); - } - - @Override - public int size() { - return map.size(); - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return map.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return inversedMap.containsKey(value); - } - - @Override - public V remove(Object key) { - V value = map.remove(key); - inversedMap.remove(value); - return value; - } - - @Override - public void putAll(Map otherMap) { - map.putAll(otherMap); - otherMap.entrySet().parallelStream().forEach(entry -> inversedMap.put(entry.getValue(),entry.getKey())); - } - - @Override - public void clear() { - map.clear(); - inversedMap.clear(); - } - - @Override - public Set keySet() { - return map.keySet(); - } - - @Override - public Collection values() { - return inversedMap.keySet(); - } - - @Override - public Set> entrySet() { - return map.entrySet(); - } - - @Override - public V getValue(K key) { - return map.get(key); - } - - @Override - public V removeKey(K key) { - return this.remove(key); - } - - @Override - public K removeValue(V value) { - K key = inversedMap.remove(value); - map.remove(key); - return key; - } - - @Override - public V getValueOrDefault(K key, V defaultValue) { - return map.getOrDefault(key, defaultValue); - } - - @Override - public K getKeyOrDefault(V value, K defaultKey) { - return inversedMap.getOrDefault(value, defaultKey); - } - - public String toString() { - return map.toString(); - } - -} diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/ResourceUtil.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/ResourceUtil.java index 53a41c2a..f37e910f 100644 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/ResourceUtil.java +++ b/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/ResourceUtil.java @@ -7,7 +7,11 @@ import java.util.Map; import java.util.Stack; +import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; @@ -16,18 +20,26 @@ import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; public class ResourceUtil { + + @Deprecated // Since 2018-08-09 private static ResourceUtil instance; + @Deprecated // Since 2018-08-09 private ResourceUtil() { } + /** + * @deprecated All methods are now static, so we do not need the synthetic + * singleton + */ + @Deprecated // Since 2018-08-09 public static ResourceUtil getInstance() { if (instance == null) instance = new ResourceUtil(); return instance; } - public R getRootObject(EObject context, Class clazz) { + public static R getRootObject(EObject context, Class clazz) { Stack stack = new Stack(); stack.push(context); while (!stack.isEmpty()) { @@ -41,17 +53,17 @@ public R getRootObject(EObject context, Class clazz) { return null; } - public E getObjectFromResourceSet(URI uri, ResourceSet resourceSet, Class clazz) { + public static E getObjectFromResourceSet(URI uri, ResourceSet resourceSet, Class clazz) { Resource res = getResource(uri, resourceSet, true); E scopingRoot = clazz.cast(res.getContents().get(0)); return scopingRoot; } - public ResourceSet getResourceSet() { + public static ResourceSet getResourceSet() { return getResourceSet("xmi"); } - public Resource getResource(URI uri, ResourceSet resourceSet, boolean load) { + public static Resource getResource(URI uri, ResourceSet resourceSet, boolean load) { try { Resource res = resourceSet.getResource(uri, false); if (res == null) { @@ -65,63 +77,81 @@ public Resource getResource(URI uri, ResourceSet resourceSet, boolean load) { } } - public Resource addToResource(URI uri, ResourceSet resourceSet, EObject obj) { + public static Resource addToResource(URI uri, ResourceSet resourceSet, EObject obj) { Resource resource = getResource(uri, resourceSet, false); resource.getContents().clear(); resource.getContents().add(obj); return resource; } - public void saveToResource(URI uri, ResourceSet resourceSet, EObject obj) { + public static void saveToResource(URI uri, ResourceSet resourceSet, EObject obj) { saveToResource(uri, resourceSet, obj, true); } - public void saveToResource(URI uri, ResourceSet resourceSet, EObject obj, boolean deleteOld) - { - try - { - Resource resource = addToResource(uri, resourceSet, obj); - if(deleteOld) { - IResource iResource = WorkspaceHelper.INSTANCE.getIResource(uri); - if(iResource.exists()) { - iResource.delete(true, null); - } -// File file = new File(filePath); -// if(file.exists()) { -// file.delete(); -// } - } - - resource.save(Collections.EMPTY_MAP); - } catch (Exception e) - { - throw new RuntimeException(e); - } - } - - public String getProjectNameFromURI(URI uri) { + public static void saveToResource(URI uri, ResourceSet resourceSet, EObject obj, boolean deleteOld) { + try { + Resource resource = addToResource(uri, resourceSet, obj); + if (deleteOld) { + IResource iResource = getIResource(uri); + if (iResource.exists()) { + iResource.delete(true, null); + } + } + + resource.save(Collections.EMPTY_MAP); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static IResource getIResource(URI uri) throws CoreException { + IProject project = getProjectByURI(uri); + project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + IPath path = getPathByURI(project, uri); + if (path.toFile().isDirectory()) + return project.getFolder(path); + else + return project.getFile(path); + } + + public static IProject getProjectByURI(URI uri) { + String projectName = getProjectNameFromURI(uri); + return org.moflon.core.utilities.WorkspaceHelper.getProjectByName(projectName); + } + + public static IPath getPathByURI(IResource iResource, URI uri) { + String[] segments = uri.segments(); + IPath path = iResource.getLocation(); + for (int index = 2; index < segments.length; ++index) { + String segment = segments[index]; + path = path.append(segment); + } + return path; + } + + public static String getProjectNameFromURI(URI uri) { return uri.segment(1); } - - public String getProjectNameFromResource(Resource resource) { + + public static String getProjectNameFromResource(Resource resource) { return getProjectNameFromURI(resource.getURI()); } - - public URI createURIFromResource(Resource resource, String folder, String file) { + + public static URI createURIFromResource(Resource resource, String folder, String file) { URI originUri = resource.getURI(); - return createURIFromOtherURI(originUri, getProjectNameFromURI(originUri), folder, file); + return createURIFromOtherURI(originUri, getProjectNameFromURI(originUri), folder, file); } - - public URI createPluginURI(String projectName, String path) { + + public static URI createPluginURI(String projectName, String path) { return URI.createPlatformPluginURI(projectName + "/" + path, false); } - - public URI createResourceURI(String projectName, String path) { + + public static URI createResourceURI(String projectName, String path) { return URI.createPlatformResourceURI(projectName + "/" + path, false); } - - public URI createURIFromOtherURI(URI originUri, String projectName, String folder, String file) { - List segments = Arrays.asList(originUri.toString().split("/")); + + public static URI createURIFromOtherURI(URI originUri, String projectName, String folder, String file) { + List segments = Arrays.asList(originUri.toString().split("/")); if (segments.size() >= 3) { String prefix = segments.get(0) + "/" + segments.get(1) + "/"; String path = prefix + projectName + "/" + folder + "/" + file; @@ -130,7 +160,7 @@ public URI createURIFromOtherURI(URI originUri, String projectName, String folde return originUri; } - public ResourceSet getResourceSet(String ext) { + public static ResourceSet getResourceSet(String ext) { ResourceSet resourceSet = new ResourceSetImpl(); Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE; Map m = reg.getExtensionToFactoryMap(); diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/WorkspaceHelper.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/WorkspaceHelper.java deleted file mode 100644 index 1b4f9c32..00000000 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/WorkspaceHelper.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.moflon.core.xtext.utils; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -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.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -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.emf.common.util.URI; -import org.eclipse.emf.ecore.resource.Resource; - -public class WorkspaceHelper { - - public final static WorkspaceHelper INSTANCE = new WorkspaceHelper(); - - private final String BIN = "bin"; - private final String SRC = "src"; - private final String SRC_GEN = "src-gen"; - private WorkspaceHelper() { - - } - - /** - * Returns the list of all projects in the workspace - */ - public List getAllProjectsInWorkspace() { - return Arrays.asList(ResourcesPlugin.getWorkspace().getRoot().getProjects()); - } - - public boolean projectExist(String projectName) { - return getProjectMonad(projectName).isPresent(); - } - - public IProject createEmptyProject(String projectName) throws CoreException { - IProgressMonitor progressMonitor = new NullProgressMonitor(); - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IProject project = root.getProject(projectName); - project.create(progressMonitor); - project.open(progressMonitor); - - return project; - } - - public IProject getProjectByName(String projectName) { - Optional monad = getProjectMonad(projectName); - return monad.isPresent()? monad.get() : null; - } - - public IResource getIResource(URI uri) throws CoreException { - IProject project = getProjectByURI(uri); - project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); - IPath path = getPathByURI(project, uri); - if(path.toFile().isDirectory()) - return project.getFolder(path); - else - return project.getFile(path); - } - - public IPath getPathByURI(IResource iResource, URI uri) { - String[] segments = uri.segments(); - IPath path = iResource.getLocation(); - for(int index = 2; index < segments.length; ++index) { - String segment = segments[index]; - path = path.append(segment); - } - return path; - } - - public IProject getProjectByURI(URI uri) { - String projectName = ResourceUtil.getInstance().getProjectNameFromURI(uri); - return getProjectByName(projectName); - } - - public IProject getProjectByResource(Resource resource) { - return getProjectByURI(resource.getURI()); - } - - private Optional getProjectMonad(String projectName){ - return getAllProjectsInWorkspace().parallelStream().filter(project -> projectName.equalsIgnoreCase(project.getName())).findFirst(); - } - - public void addNature(IProject project, String natureID) throws CoreException { - IProjectDescription description = project.getDescription(); - - String[] natures = description.getNatureIds(); - String[] newNatures = new String[natures.length + 1]; - System.arraycopy(natures, 0, newNatures, 0, natures.length); - newNatures[natures.length] = natureID; - - // validate the natures - IWorkspace workspace = ResourcesPlugin.getWorkspace(); - IStatus status = workspace.validateNatureSet(newNatures); - - // only apply new nature, if the status is ok - if (status.getCode() == IStatus.OK) { - description.setNatureIds(newNatures); - project.setDescription(description, null); - } - } - - public IFolder getSrcFolder(IProject project) { - return project.getFolder(SRC); - } - - public IFolder getBinFolder(IProject project) { - return project.getFolder(BIN); - } - - public IFolder getSrcGenFolder(IProject project) { - return project.getFolder(SRC_GEN); - } - - public IFolder getSubFolderFromQualifiedName(IFolder folder, String qualifiedName) { - List parts = Arrays.asList(qualifiedName.split("\\.")); - IFolder current = folder; - IFolder parent; - for(String part : parts) { - parent = current; - current = parent.getFolder(part); - if(!current.exists()) { - return parent; - } - } - return current; - } - -} diff --git a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/XtextUtil.java b/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/XtextUtil.java deleted file mode 100644 index 61d60171..00000000 --- a/org.moflon.core.xtext/src/org/moflon/core/xtext/utils/XtextUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.moflon.core.xtext.utils; - -import java.util.Collection; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import org.eclipse.emf.ecore.EDataType; -import org.eclipse.emf.ecore.EPackage; -import org.eclipse.emf.ecore.EcoreFactory; -import org.eclipse.emf.ecore.util.EcoreUtil; - -public class XtextUtil -{ - private static XtextUtil instance; - - private final EPackage ecoreEPackage; - - private final List ecoreEDataTypes; - - private XtextUtil(){ - ecoreEPackage = EcoreFactory.eINSTANCE.getEcorePackage(); - EcoreUtil.resolveAll(ecoreEPackage); - ecoreEDataTypes = mapToSubtype(ecoreEPackage.getEClassifiers(), EDataType.class); - } - - public static XtextUtil getInstance(){ - if(instance == null) - instance = new XtextUtil(); - return instance; - } - - public List mapToSupertypeUnsorted(Collection subTypes, Class clazz) - { - return subTypes.parallelStream().map(clazz::cast).collect(Collectors.toList()); - } - - public List mapToSubtypeUnsorted(Collection list, Class clazz) - { - return list.parallelStream().filter(clazz::isInstance).map(clazz::cast).collect(Collectors.toList()); - } - - public List mapToSubtype(Collection list, Class clazz) - { - return list.stream().filter(clazz::isInstance).map(clazz::cast).collect(Collectors.toList()); - } - - public boolean exist(List list, Predicate predicate){ - return list.stream().anyMatch(predicate); - } - - public List mapToSupertype(Collection subTypes, Class clazz) - { - return subTypes.stream().map(clazz::cast).collect(Collectors.toList()); - } - - public final EPackage getEcoreEPackage(){ - return ecoreEPackage; - } - - public final List getEcoreEDataTypes(){ - return ecoreEDataTypes; - } - -} diff --git a/org.moflon.emf.build/META-INF/MANIFEST.MF b/org.moflon.emf.build/META-INF/MANIFEST.MF index bc755031..8b6193e1 100644 --- a/org.moflon.emf.build/META-INF/MANIFEST.MF +++ b/org.moflon.emf.build/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Basic EMF build infrastructure Bundle-SymbolicName: org.moflon.emf.build;singleton:=true -Bundle-Version: 1.1.0.qualifier +Bundle-Version: 1.2.0.qualifier Bundle-Vendor: eMoflon developers Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.core.runtime;bundle-version="3.0.0", diff --git a/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfBuilder.java b/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfBuilder.java index c0ef3ab3..5db0cff6 100644 --- a/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfBuilder.java +++ b/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfBuilder.java @@ -1,6 +1,9 @@ package org.moflon.emf.build; +import java.io.IOException; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.log4j.Logger; import org.eclipse.core.resources.IBuildConfiguration; @@ -9,6 +12,7 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; 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.MultiStatus; @@ -17,7 +21,12 @@ import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +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.ecore.util.EcoreUtil; import org.gervarro.eclipse.workspace.util.AntPatternCondition; import org.moflon.core.build.AbstractVisitorBuilder; import org.moflon.core.build.CleanVisitor; @@ -47,7 +56,7 @@ public class MoflonEmfBuilder extends AbstractVisitorBuilder { * This builder gets triggered whenever any ecore file in /models changes */ public MoflonEmfBuilder() { - super(new AntPatternCondition(new String[] { "model/*.ecore" })); + super(new AntPatternCondition(new String[] { "model/*.ecore", "model/*.xcore" })); } public static String getId() { @@ -79,10 +88,8 @@ protected void clean(final IProgressMonitor monitor) throws CoreException { /** * Converts the given {@link Status} to problem markers in the Eclipse UI * - * @param status - * the status to be converted - * @param file - * the file contains problems + * @param status the status to be converted + * @param file the file contains problems */ public void handleErrorsInEclipse(final IStatus status, final IFile file) { final String reporterClass = "org.moflon.core.ui.errorhandling.MultiStatusAwareErrorReporter"; @@ -96,64 +103,107 @@ public void handleErrorsInEclipse(final IStatus status, final IFile file) { } @Override - protected void processResource(final IResource ecoreResource, final int kind, Map args, + protected void processResource(IResource resource, final int kind, Map args, final IProgressMonitor monitor) { - if (WorkspaceHelper.isEcoreFile(ecoreResource)) { - final IFile ecoreFile = Platform.getAdapterManager().getAdapter(ecoreResource, IFile.class); - final MultiStatus emfBuilderStatus = new MultiStatus(WorkspaceHelper.getPluginId(getClass()), 0, - "Problems during EMF code generation", null); - try { - final SubMonitor subMon = SubMonitor.convert(monitor, - "Generating code for project " + getProject().getName(), 13); - - final IProject project = getProject(); - createFoldersIfNecessary(project, subMon.split(1)); - ClasspathUtil.makeSourceFolderIfNecessary(WorkspaceHelper.getGenFolder(getProject())); - ClasspathUtil.makeSourceFolderIfNecessary(WorkspaceHelper.getInjectionFolder(getProject())); - - // Compute project dependencies - final IBuildConfiguration[] referencedBuildConfigs = project - .getReferencedBuildConfigs(project.getActiveBuildConfig().getName(), false); - for (final IBuildConfiguration referencedConfig : referencedBuildConfigs) { - addTriggerProject(referencedConfig.getProject()); - } - - // Remove markers and delete generated code - deleteProblemMarkers(); - removeGeneratedCode(project); - - // Build - final ResourceSet resourceSet = eMoflonEMFUtil.createDefaultResourceSet(); - eMoflonEMFUtil.installCrossReferencers(resourceSet); - subMon.worked(1); - - final MoflonEmfCodeGenerator codeGenerationTask = new MoflonEmfCodeGenerator(ecoreFile, resourceSet, - EMoflonPreferencesActivator.getDefault().getPreferencesStorage()); - - final IStatus status = codeGenerationTask.run(subMon.split(1)); - subMon.worked(3); - emfBuilderStatus.add(status); - - if (!emfBuilderStatus.isOK()) - return; - - final GenModel genModel = codeGenerationTask.getGenModel(); - if (genModel == null) { - emfBuilderStatus.add(new Status(IStatus.ERROR, WorkspaceHelper.getPluginId(getClass()), - String.format("No GenModel found for '%s'", getProject()))); - } else { - ExportedPackagesInManifestUpdater.updateExportedPackageInManifest(project, genModel); - - PluginXmlUpdater.updatePluginXml(project, genModel, subMon.split(1)); - } - ResourcesPlugin.getWorkspace().checkpoint(false); - - } catch (final CoreException e) { - emfBuilderStatus.add(new Status(e.getStatus().getSeverity(), WorkspaceHelper.getPluginId(getClass()), - e.getMessage(), e)); - } finally { - handleErrorsInEclipse(emfBuilderStatus, ecoreFile); + + if (WorkspaceHelper.isXcoreFile(resource)) + resource = convertXcoreToEcore(resource); + + if (WorkspaceHelper.isEcoreFile(resource)) + buildEcoreFile(resource, monitor); + } + + private void buildEcoreFile(IResource resource, final IProgressMonitor monitor) { + final IFile ecoreFile = Platform.getAdapterManager().getAdapter(resource, IFile.class); + final MultiStatus emfBuilderStatus = new MultiStatus(WorkspaceHelper.getPluginId(getClass()), 0, + "Problems during EMF code generation", null); + try { + final SubMonitor subMon = SubMonitor.convert(monitor, + "Generating code for project " + getProject().getName(), 13); + + final IProject project = getProject(); + createFoldersIfNecessary(project, subMon.split(1)); + ClasspathUtil.makeSourceFolderIfNecessary(WorkspaceHelper.getGenFolder(getProject())); + ClasspathUtil.makeSourceFolderIfNecessary(WorkspaceHelper.getInjectionFolder(getProject())); + + // Compute project dependencies + final IBuildConfiguration[] referencedBuildConfigs = project + .getReferencedBuildConfigs(project.getActiveBuildConfig().getName(), false); + for (final IBuildConfiguration referencedConfig : referencedBuildConfigs) { + addTriggerProject(referencedConfig.getProject()); + } + + // Remove markers and delete generated code + deleteProblemMarkers(); + removeGeneratedCode(project); + + // Build + final ResourceSet resourceSet = eMoflonEMFUtil.createDefaultResourceSet(); + eMoflonEMFUtil.installCrossReferencers(resourceSet); + subMon.worked(1); + + final MoflonEmfCodeGenerator codeGenerationTask = new MoflonEmfCodeGenerator(ecoreFile, resourceSet, + EMoflonPreferencesActivator.getDefault().getPreferencesStorage()); + + final IStatus status = codeGenerationTask.run(subMon.split(1)); + subMon.worked(3); + emfBuilderStatus.add(status); + + if (!emfBuilderStatus.isOK()) + return; + + final GenModel genModel = codeGenerationTask.getGenModel(); + if (genModel == null) { + emfBuilderStatus.add(new Status(IStatus.ERROR, WorkspaceHelper.getPluginId(getClass()), + String.format("No GenModel found for '%s'", getProject()))); + } else { + ExportedPackagesInManifestUpdater.updateExportedPackageInManifest(project, genModel); + + PluginXmlUpdater.updatePluginXml(project, genModel, subMon.split(1)); } + ResourcesPlugin.getWorkspace().checkpoint(false); + + } catch (final CoreException e) { + emfBuilderStatus.add(new Status(e.getStatus().getSeverity(), WorkspaceHelper.getPluginId(getClass()), + e.getMessage(), e)); + } finally { + handleErrorsInEclipse(emfBuilderStatus, ecoreFile); + } + } + + private IResource convertXcoreToEcore(IResource resource) { + ResourceSet rs = new ResourceSetImpl(); + final URI projectURI = URI.createPlatformResourceURI(getProject().getName() + "/", true); + final URI resourceURI = URI.createURI(resource.getProjectRelativePath().toString()).resolve(projectURI); + Resource r = rs.createResource(resourceURI); + + try { + r.load(null); + + List metamodels = r.getContents()// + .stream()// + .filter(EPackage.class::isInstance)// + .map(EPackage.class::cast)// + .collect(Collectors.toList()); + + EcoreUtil.resolveAll(r); + + IPath ecoreFilePath = resource.getProjectRelativePath().removeFileExtension().addFileExtension("ecore"); + final URI ecoreFileURI = URI.createURI(ecoreFilePath.toString()).resolve(projectURI); + Resource ecoreFileResource = rs.createResource(ecoreFileURI); + + if (metamodels.size() != 1) { + throw new IllegalStateException( + "Xcore file " + resource.getName() + " must contain exactly one package."); + } + + ecoreFileResource.getContents().add(metamodels.get(0)); + ecoreFileResource.save(null); + + return getProject().getFile(ecoreFilePath); + } catch (IOException e) { + e.printStackTrace(); + return resource; } } @@ -165,8 +215,7 @@ protected final AntPatternCondition getTriggerCondition(final IProject project) /** * Handles errors and warning produced by the code generation task * - * @param status - * the {@link IStatus} that contains the errors and warnings + * @param status the {@link IStatus} that contains the errors and warnings */ protected void handleErrorsAndWarnings(final IStatus status, final IFile ecoreFile) throws CoreException { if (status.matches(IStatus.ERROR)) { @@ -180,10 +229,8 @@ protected void handleErrorsAndWarnings(final IStatus status, final IFile ecoreFi /** * Removes all contents in /gen, but preserves all versioning files * - * @param project - * the project to be cleaned - * @throws CoreException - * if cleaning fails + * @param project the project to be cleaned + * @throws CoreException if cleaning fails */ private void removeGeneratedCode(final IProject project) throws CoreException { final CleanVisitor cleanVisitor = new CleanVisitor(project, // diff --git a/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfCodeGenerator.java b/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfCodeGenerator.java index f07e29d2..44c672a2 100644 --- a/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfCodeGenerator.java +++ b/org.moflon.emf.build/src/org/moflon/emf/build/MoflonEmfCodeGenerator.java @@ -64,12 +64,14 @@ public IStatus processResource(final IProgressMonitor monitor) { if (genModelBuilderStatus.matches(IStatus.ERROR)) { return genModelBuilderStatus; } - this.genModel = genModelBuilderJob.getGenModel(); + this.setGenModel(genModelBuilderJob.getGenModel()); // Load injections final IProject project = getEcoreFile().getProject(); - final IStatus injectionStatus = createInjections(project, genModel); + final InjectionManager injectionManager = createInjectionManager(project); + this.setInjectorManager(injectionManager); + final IStatus injectionStatus = createInjections(project); if (subMon.isCanceled()) { return Status.CANCEL_STATUS; } @@ -105,17 +107,25 @@ public IStatus processResource(final IProgressMonitor monitor) { } } + protected final void setGenModel(final GenModel genModel) { + this.genModel = genModel; + } + public final GenModel getGenModel() { return genModel; } + protected void setInjectorManager(final InjectionManager injectionManager) { + this.injectionManager = injectionManager; + } + public final InjectionManager getInjectorManager() { return injectionManager; } /** * Returns the project name to be displayed - * + * * @param moflonProperties * the properties container to consult * @return the project name @@ -125,17 +135,30 @@ protected String getFullProjectName(final MoflonPropertiesContainer moflonProper } /** - * Loads the injections from the /injection folder + * Loads the injections from the /injection folder using the injection manager returned from {@link #getInjectorManager()} */ - private IStatus createInjections(final IProject project, final GenModel genModel) throws CoreException { + protected IStatus createInjections(final IProject project) throws CoreException { + final IStatus extractionStatus = getInjectorManager().extractInjections(); + return extractionStatus; + } + + /** + * Creates the injection manager to be used for this build process + * + * The resulting injection manager still needs to be set using {@link #setInjectorManager(InjectionManager)} + * @param project the current project + * @return + * @throws CoreException + */ + protected InjectionManager createInjectionManager(final IProject project) + throws CoreException { final IFolder injectionFolder = WorkspaceHelper.addFolder(project, WorkspaceHelper.INJECTION_FOLDER, new NullProgressMonitor()); final CodeInjector injector = new CodeInjectorImpl(project.getLocation().toOSString()); - final InjectionExtractor injectionExtractor = new XTextInjectionExtractor(injectionFolder, genModel); + final InjectionExtractor injectionExtractor = new XTextInjectionExtractor(injectionFolder, this.getGenModel()); - injectionManager = new InjectionManager(injectionExtractor, injector); - final IStatus extractionStatus = injectionManager.extractInjections(); - return extractionStatus; + InjectionManager injectionManager = new InjectionManager(injectionExtractor, injector); + return injectionManager; } } diff --git a/org.moflon.emf.build/src/org/moflon/emf/build/MonitoredGenModelBuilder.java b/org.moflon.emf.build/src/org/moflon/emf/build/MonitoredGenModelBuilder.java index 596f3ae1..0242a624 100644 --- a/org.moflon.emf.build/src/org/moflon/emf/build/MonitoredGenModelBuilder.java +++ b/org.moflon.emf.build/src/org/moflon/emf/build/MonitoredGenModelBuilder.java @@ -18,6 +18,7 @@ import org.gervarro.eclipse.task.ITask; import org.moflon.core.propertycontainer.MoflonPropertiesContainer; import org.moflon.core.propertycontainer.MoflonPropertiesContainerHelper; +import org.moflon.core.utilities.ExceptionUtil; import org.moflon.core.utilities.WorkspaceHelper; import org.moflon.core.utilities.eMoflonEMFUtil; import org.moflon.emf.codegen.MoflonGenModelBuilder; @@ -80,7 +81,7 @@ public final IStatus run(final IProgressMonitor monitor) { try { this.genModel = genModelBuilder.buildGenModel(genModelURI); } catch (final RuntimeException e) { - return new Status(IStatus.ERROR, WorkspaceHelper.getPluginId(getClass()), e.getMessage(), e); + return handleExceptionDuringGenmodelProcessing(e); } subMon.worked(30); @@ -117,6 +118,25 @@ public final IStatus run(final IProgressMonitor monitor) { return saveStatus.isOK() ? Status.OK_STATUS : saveStatus; } + /** + * This method creates an {@link IStatus} from the given {@link Exception} + * + * If the cause of the {@link Exception} is a {@link ResourceException}, an informative message for the user is created. + * + * @param e the {@link Exception} to report + * @return the resulting {@link IStatus} + */ + @SuppressWarnings("restriction") + private IStatus handleExceptionDuringGenmodelProcessing(final Exception e) { + final Throwable cause = e.getCause(); + if (cause != null && cause instanceof org.eclipse.core.internal.resources.ResourceException) { + final String message = "A required resource could not be found. This may mean that a required project could not be built. Please fix the required resource first and then rebuild this project. Details: " + cause.getMessage(); + return new Status(IStatus.ERROR, WorkspaceHelper.getPluginId(getClass()), message); + } else { + return ExceptionUtil.createDefaultErrorStatus(getClass(), e); + } + } + /** * Saves the {@link GenModel} of this builder * diff --git a/org.moflon.emf.codegen/build.properties b/org.moflon.emf.codegen/build.properties index 631108b9..bf298db6 100644 --- a/org.moflon.emf.codegen/build.properties +++ b/org.moflon.emf.codegen/build.properties @@ -3,7 +3,6 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ src/,\ - plugin.xml,\ schema/,\ .project,\ .gitignore,\ diff --git a/org.moflon.emf.codegen/plugin.xml b/org.moflon.emf.codegen/plugin.xml deleted file mode 100644 index ce1e986c..00000000 --- a/org.moflon.emf.codegen/plugin.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/org.moflon.emf.injection/src/org/moflon/emf/injection/unparsing/MethodVisitor.java b/org.moflon.emf.injection/src/org/moflon/emf/injection/unparsing/MethodVisitor.java index 18e46ca2..6c594e88 100644 --- a/org.moflon.emf.injection/src/org/moflon/emf/injection/unparsing/MethodVisitor.java +++ b/org.moflon.emf.injection/src/org/moflon/emf/injection/unparsing/MethodVisitor.java @@ -24,7 +24,7 @@ public class MethodVisitor extends ASTVisitor { private final List methods = new ArrayList(); public MethodVisitor(final String codeForProject) { - final ASTParser parser = ASTParser.newParser(AST.JLS8); + final ASTParser parser = ASTParser.newParser(getJLSLevel()); parser.setSource(codeForProject.toCharArray()); parser.setIgnoreMethodBodies(true); @@ -33,6 +33,15 @@ public MethodVisitor(final String codeForProject) { node.accept(this); } + /** + * Returns the JLS level to be used by the {@link MethodVisitor} + * @return the JLS level + */ + @SuppressWarnings("deprecation") + private int getJLSLevel() { + return AST.JLS8; + } + @Override public boolean visit(final MethodDeclaration node) { methods.add(node); diff --git a/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/DefaultEPackageContentGenerator.xtend b/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/DefaultContentGenerator.xtend similarity index 58% rename from org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/DefaultEPackageContentGenerator.xtend rename to org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/DefaultContentGenerator.xtend index a34a2de4..3a8b3aab 100644 --- a/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/DefaultEPackageContentGenerator.xtend +++ b/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/DefaultContentGenerator.xtend @@ -3,7 +3,7 @@ package org.moflon.emf.ui.wizard /** * This class provides the default content of generated Ecore files */ -class DefaultEPackageContentGenerator { +class DefaultContentGenerator { /** * Generates an XMI representation of the EPackage corresponding to the given @@ -14,7 +14,7 @@ class DefaultEPackageContentGenerator { * @param packageUri the NS URI of the EPackage * @return the raw XMI file content */ - public static def String generateDefaultEPackageForProject(String projectName, String packageName, String packageUri) { + static def String generateDefaultEPackageForProject(String projectName, String packageName, String packageUri) { ''' ''' } + + /** + * Generates a default Xcore file representation of the EPackage corresponding to the given + * project name + * + * @param projectName the name of the containing project + * @param packageName the name of the EPackage + * @param packageUri the NS URI of the EPackage + * @return the default Xcore file's content as a string + */ + static def String generateDefaultXCoreFileForProject(String projectName, String packageName, String packageUri) { + ''' + @xcore.lang.Ecore(nsURI="«packageUri»", nsPrefix="«projectName»") + @xcore.lang.GenModel(modelDirectory="/«projectName»/gen") + package «packageName» + ''' + } } \ No newline at end of file diff --git a/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/NewMoflonEmfProjectWizard.java b/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/NewMoflonEmfProjectWizard.java index a7ade4e0..bd1ec378 100644 --- a/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/NewMoflonEmfProjectWizard.java +++ b/org.moflon.emf.ui/src/org/moflon/emf/ui/wizard/NewMoflonEmfProjectWizard.java @@ -106,8 +106,14 @@ protected void generateDefaultFiles(final IProgressMonitor monitor, final IProje final String packageName = MoflonUtil.lastSegmentOf(projectName); final URI projectUri = MoflonGenModelBuilder.determineProjectUriBasedOnPreferences(project); final URI packageUri = URI.createURI(projectUri.toString() + MoflonConventions.getDefaultPathToEcoreFileInProject(projectName)); - final String defaultEcoreFile = DefaultEPackageContentGenerator.generateDefaultEPackageForProject(projectName, packageName, packageUri.toString()); - WorkspaceHelper.addFile(project, MoflonConventions.getDefaultPathToEcoreFileInProject(projectName), defaultEcoreFile, subMon.split(1)); + + if(projectInfo.generateDefaultXCoreFile()) { + final String defaultXcoreFile = DefaultContentGenerator.generateDefaultXCoreFileForProject(projectName, packageName, packageUri.toString()); + WorkspaceHelper.addFile(project, MoflonConventions.getDefaultPathToFileInProject(projectName, ".xcore"), defaultXcoreFile, subMon.split(1)); + } else { + final String defaultEcoreFile = DefaultContentGenerator.generateDefaultEPackageForProject(projectName, packageName, packageUri.toString()); + WorkspaceHelper.addFile(project, MoflonConventions.getDefaultPathToEcoreFileInProject(projectName), defaultEcoreFile, subMon.split(1)); + } } /** diff --git a/pom.xml b/pom.xml index ce5d5ca0..461f55a3 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ org.moflon.core.ui org.moflon.core.ui.autosetup org.moflon.core.ui.autosetup.tests + org.moflon.core.ui.packageregistration org.moflon.core.releng.updatesite org.moflon.core.releng.target org.moflon.core.utilities diff --git a/projectSet.psf b/projectSet.psf index 8bead36b..e60911be 100644 --- a/projectSet.psf +++ b/projectSet.psf @@ -16,6 +16,7 @@ + @@ -46,6 +47,7 @@ +