Skip to content

Commit

Permalink
Intoduces a MagicDrawProjectScope #25
Browse files Browse the repository at this point in the history
This change provides a new scope and engine context implementation that
refreshes the base index when new projects are attached to the handled
MagicDraw project.
  • Loading branch information
ujhelyiz committed Mar 1, 2019
1 parent 476ca9c commit ca2562d
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,28 +1,56 @@
package com.incquerylabs.v4md;

import com.incquerylabs.v4md.expressions.BinaryVQLExpression;
import com.nomagic.ci.persistence.IProject;
import com.nomagic.magicdraw.core.Application;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.core.ProjectUtilities;
import com.nomagic.magicdraw.core.modules.ModuleUsage;
import com.nomagic.magicdraw.core.project.ProjectEventListenerAdapter;
import com.nomagic.magicdraw.core.project.ProjectPartListener;
import com.nomagic.magicdraw.expressions.evaluation.ExpressionEvaluationConfigurator;

public class V4MDPlugin extends com.nomagic.magicdraw.plugins.Plugin {

private final class V4MDProjectListener extends ProjectEventListenerAdapter implements ProjectPartListener {
@Override
public void projectOpened(Project project) {
ViatraQueryAdapter.getOrCreateAdapter(project);
}

@Override
public void projectPreClosed(Project project) {
ViatraQueryAdapter.disposeAdapter(project);
}

@Override
public void projectPartLoaded(Project project, IProject storage) {
ViatraQueryAdapter.getAdapter(project).ifPresent(ViatraQueryAdapter::projectStructureUpdated);
}

@Override
public void projectPartAttached(ModuleUsage usage) {
Project project = ProjectUtilities.getProject(usage.getUsed());
ViatraQueryAdapter.getAdapter(project).ifPresent(ViatraQueryAdapter::projectStructureUpdated);
}

@Override
public void projectPartDetached(ModuleUsage usage) {
Project project = ProjectUtilities.getProject(usage.getUsed());
ViatraQueryAdapter.getAdapter(project).ifPresent(ViatraQueryAdapter::projectStructureUpdated);

}

@Override
public void projectPartRemoved(IProject project) {
Project modelProject = ProjectUtilities.getProject(project);
ViatraQueryAdapter.getAdapter(modelProject).ifPresent(ViatraQueryAdapter::projectStructureUpdated);
}
}

@Override
public void init() {
Application.getInstance().getProjectsManager().addProjectListener(new ProjectEventListenerAdapter() {

@Override
public void projectOpened(Project project) {
ViatraQueryAdapter.getOrCreateAdapter(project);
}

@Override
public void projectPreClosed(Project project) {
ViatraQueryAdapter.disposeAdapter(project);
}

});
Application.getInstance().getProjectsManager().addProjectListener(new V4MDProjectListener());

// Registers an expression evaluator for generated VIATRA queries
ExpressionEvaluationConfigurator.getInstance().registerFactory(BinaryVQLExpression.LANGUAGE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@

package com.incquerylabs.v4md;

import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions;
import org.eclipse.viatra.query.runtime.emf.EMFScope;
import org.eclipse.viatra.query.runtime.exception.ViatraQueryException;

import com.incquerylabs.v4md.internal.MagicDrawProjectScope;
import com.nomagic.magicdraw.core.Project;

public class ViatraQueryAdapter extends AdapterImpl{

private AdvancedViatraQueryEngine engine;
private Project project;



private ViatraQueryAdapter(AdvancedViatraQueryEngine engine, Project project) {
this.engine = engine;
Expand All @@ -43,6 +35,13 @@ public void wipeEngine(){
engine.wipe();
}

/**
* This method is called by a project change listener registered by V4MD; it should not be called by end-users.
*/
void projectStructureUpdated() {
((MagicDrawProjectScope)engine.getScope()).projectStructureUpdated();
}

public static Optional<ViatraQueryAdapter> getAdapter(Project project) {
Objects.requireNonNull(project, "ViatraQueryAdapter cannot be provided for a null Project");
return Optional.ofNullable(project.getPrimaryModel()).flatMap(m -> m.eAdapters().stream()
Expand All @@ -51,15 +50,10 @@ public static Optional<ViatraQueryAdapter> getAdapter(Project project) {

public static ViatraQueryAdapter getOrCreateAdapter(Project project, Notifier... notifiers) {
return getAdapter(project).orElseGet(() -> {
ViatraQueryAdapter adapter =
new ViatraQueryAdapter(
createQueryEngine(
Stream.concat(project.getModels().stream(), Arrays.stream(notifiers))
.collect(Collectors.toSet())),
project);
project.getPrimaryModel().eAdapters().add(adapter);
return adapter;
});
ViatraQueryAdapter adapter = new ViatraQueryAdapter(createQueryEngine(project, notifiers), project);
project.getPrimaryModel().eAdapters().add(adapter);
return adapter;
});
}

public static ViatraQueryAdapter getOrCreateAdapter(Project project) {
Expand All @@ -69,16 +63,8 @@ public static ViatraQueryAdapter getOrCreateAdapter(Project project) {
public static void disposeAdapter(Project project) {
getAdapter(project).ifPresent(ViatraQueryAdapter::dispose);
}


private static AdvancedViatraQueryEngine createQueryEngine(Set<? extends Notifier> setOfRootElement) throws ViatraQueryException {
// XXX Omitting references can cause semantic errors (so far we are in the clear though)
// these references are only present in UML profiles, typically their contents are equal to the original references inherited from the UML type hierarchy, however there are some cases when this might not be the case.
BaseIndexOptions options = new BaseIndexOptions()
.withFeatureFilterConfiguration(reference -> reference instanceof EReference
&& ((EReference) reference).isContainment() && reference.getName().contains("_from_"))
.withStrictNotificationMode(false);

return AdvancedViatraQueryEngine.createUnmanagedEngine(new EMFScope(setOfRootElement, options));
private static AdvancedViatraQueryEngine createQueryEngine(Project project, Notifier... notifiers) throws ViatraQueryException {
return AdvancedViatraQueryEngine.createUnmanagedEngine(new MagicDrawProjectScope(project, notifiers));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.incquerylabs.v4md.internal;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine;
import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex;
import org.eclipse.viatra.query.runtime.api.scope.IEngineContext;
import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener;
import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions;
import org.eclipse.viatra.query.runtime.base.api.NavigationHelper;
import org.eclipse.viatra.query.runtime.base.api.filters.IBaseIndexObjectFilter;
import org.eclipse.viatra.query.runtime.base.api.filters.IBaseIndexResourceFilter;
import org.eclipse.viatra.query.runtime.base.core.NavigationHelperImpl;
import org.eclipse.viatra.query.runtime.base.exception.ViatraBaseException;
import org.eclipse.viatra.query.runtime.emf.DynamicEMFQueryRuntimeContext;
import org.eclipse.viatra.query.runtime.emf.EMFBaseIndexWrapper;
import org.eclipse.viatra.query.runtime.emf.EMFQueryRuntimeContext;
import org.eclipse.viatra.query.runtime.matchers.ViatraQueryRuntimeException;
import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext;

import com.google.common.collect.Sets;
import com.incquerylabs.v4md.internal.MagicDrawProjectScope.IProjectChangedListener;

/**
* Provides a specific engine context implementation for MagicDraw projects.
*
* The implementation is based on EMFEngineContext from VIATRA 2.1.1
*/
class MagicDrawProjectEngineContext implements IEngineContext {

private final MagicDrawProjectScope scope;
ViatraQueryEngine engine;
Logger logger;
MagicDrawProjectNavigationHelper navHelper;
IBaseIndex baseIndex;
IIndexingErrorListener taintListener;
private EMFQueryRuntimeContext runtimeContext;

private IProjectChangedListener scopeListener = this::modelSetUpdated;

private void modelSetUpdated() {
Set<Notifier> customNotifiers = scope.getCustomNotifiers().collect(Collectors.toSet());
Set<Notifier> projectRoots = scope.getProjectModels().collect(Collectors.toSet());
MagicDrawProjectNavigationHelper navigationHelper = getNavHelper(true);
Set<Notifier> actualModelRoots = navigationHelper.getModelRoots();

Set<Notifier> rootsToAdd = Sets.difference(projectRoots, actualModelRoots);
Set<Notifier> rootsToRemove = Sets.difference(Sets.difference(actualModelRoots, projectRoots), customNotifiers);

try {
navigationHelper.coalesceTraversals(() -> {
while (!rootsToRemove.isEmpty()) {

This comment has been minimized.

Copy link
@bergmanngabor

bergmanngabor Mar 1, 2019

Member

why not for (root: rootsToRemove) navigationHelper.removeRoot(root) ? do we expect the set rootsToRemove to change during this process?

This comment has been minimized.

Copy link
@ujhelyiz

ujhelyiz Mar 1, 2019

Author Member

Yes, we do: the sets are initialized with Sets.difference that returns views that change when their source connections change.

navigationHelper.removeRoot(rootsToRemove.iterator().next());
}
while (!rootsToAdd.isEmpty()) {
navigationHelper.addRoot(rootsToAdd.iterator().next());
}
});
} catch (InvocationTargetException e) {
logger.fatal("Error while updating project", e);
}
}

/**
* Customizes the NavigationHelperImpl by adding a new removeRoot method
*
*/
private class MagicDrawProjectNavigationHelper extends NavigationHelperImpl {

public MagicDrawProjectNavigationHelper(Notifier emfRoot, BaseIndexOptions options, Logger logger) {
super(emfRoot, options, logger);
}

Set<Notifier> getModelRoots() {
return modelRoots;
}

public void removeRoot(Notifier root) {
if (!((root instanceof EObject) || (root instanceof Resource) || (root instanceof ResourceSet))) {
throw new ViatraBaseException(ViatraBaseException.INVALID_EMFROOT);
}

if (!modelRoots.contains(root))
return;

if (root instanceof Resource) {
IBaseIndexResourceFilter resourceFilter = getBaseIndexOptions().getResourceFilterConfiguration();
if (resourceFilter != null && resourceFilter.isResourceFiltered((Resource) root))
return;
}
final IBaseIndexObjectFilter objectFilter = getBaseIndexOptions().getObjectFilterConfiguration();
if (objectFilter != null && objectFilter.isFiltered(root))
return;

// no veto by filters
modelRoots.add(root);

This comment has been minimized.

Copy link
@bergmanngabor

bergmanngabor Mar 1, 2019

Member

why not remove?

This comment has been minimized.

Copy link
@ujhelyiz

ujhelyiz Mar 1, 2019

Author Member

Good catch, that is a quite bad copy-paste error.

// TODO contentAdapter.removeAdapter(root); removeAdapter is not visible here

This comment has been minimized.

Copy link
@bergmanngabor

bergmanngabor Mar 1, 2019

Member

I believe the intended way to remove and adapter would be root.eAdapters().remove(contentAdapter); is there a specific reason why we need to circumvent it?

This comment has been minimized.

Copy link
@ujhelyiz

ujhelyiz Mar 1, 2019

Author Member

I tried to copy the structure of NavigationHelperImpl#addRoot here; we might want to add this back the upstream VIATRA where it is beneficial to have the same structure in both cases.

Method method;
try {
method = contentAdapter.getClass().getMethod("removeAdaper", Notifier.class);
method.setAccessible(true);
method.invoke(contentAdapter, root);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
logger.fatal("Error while updating project", e);
}
notifyBaseIndexChangeListeners();
}
}

public MagicDrawProjectEngineContext(MagicDrawProjectScope scope, ViatraQueryEngine engine, IIndexingErrorListener taintListener, Logger logger) {
this.scope = scope;
this.engine = engine;
this.logger = logger;
this.taintListener = taintListener;
scope.addProjectChangeListener(scopeListener);
}

/**
* @throws ViatraQueryRuntimeException thrown if the navigation helper cannot be initialized
*/
public NavigationHelper getNavHelper() {
return getNavHelper(true);
}

private MagicDrawProjectNavigationHelper getNavHelper(boolean ensureInitialized) {
if (navHelper == null) {
navHelper = new MagicDrawProjectNavigationHelper(null, this.scope.getOptions(), logger);


getBaseIndex().addIndexingErrorListener(taintListener);
if (ensureInitialized) {
ensureIndexLoaded();
}
}
return navHelper;
}

private void ensureIndexLoaded() {
for (Notifier scopeRoot : this.scope.getScopeRoots()) {
navHelper.addRoot(scopeRoot);
}
}

@Override
public IQueryRuntimeContext getQueryRuntimeContext() {
NavigationHelper nh = getNavHelper(false);
if (runtimeContext == null) {
runtimeContext =
scope.getOptions().isDynamicEMFMode() ?
new DynamicEMFQueryRuntimeContext(nh, logger, scope) :
new EMFQueryRuntimeContext(nh, logger, scope);

ensureIndexLoaded();
}

return runtimeContext;
}

@Override
public void dispose() {
if (runtimeContext != null) runtimeContext.dispose();
if (navHelper != null) navHelper.dispose();

scope.removeProjectChangeListener(scopeListener);

this.baseIndex = null;
this.engine = null;
this.logger = null;
this.navHelper = null;
}


@Override
public IBaseIndex getBaseIndex() {
if (baseIndex == null) {
final NavigationHelper navigationHelper = getNavHelper();
baseIndex = new EMFBaseIndexWrapper(navigationHelper);
}
return baseIndex;
}

}
Loading

0 comments on commit ca2562d

Please sign in to comment.