/******************************************************************************* | |
* Copyright (c) 2011-2017 The University of York, Aston University. | |
* | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License 2.0 which is available at | |
* http://www.eclipse.org/legal/epl-2.0. | |
* | |
* This Source Code may also be made available under the following Secondary | |
* Licenses when the conditions for such availability set forth in the Eclipse | |
* Public License, v. 2.0 are satisfied: GNU General Public License, version 3. | |
* | |
* SPDX-License-Identifier: EPL-2.0 OR GPL-3.0 | |
* | |
* Contributors: | |
* Konstantinos Barmpis - initial API and implementation | |
* Antonio Garcia-Dominguez - cleanup and bug fixes, refactor into two levels | |
******************************************************************************/ | |
package org.eclipse.hawk.epsilon.emc; | |
import java.lang.ref.WeakReference; | |
import java.lang.reflect.Array; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import java.util.NoSuchElementException; | |
import java.util.Objects; | |
import java.util.Set; | |
import java.util.WeakHashMap; | |
import java.util.function.Consumer; | |
import org.eclipse.epsilon.common.parse.problem.ParseProblem; | |
import org.eclipse.epsilon.eol.EolModule; | |
import org.eclipse.epsilon.eol.IEolModule; | |
import org.eclipse.epsilon.eol.exceptions.EolInternalException; | |
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException; | |
import org.eclipse.epsilon.eol.exceptions.EolUndefinedVariableException; | |
import org.eclipse.epsilon.eol.exceptions.models.EolModelElementTypeNotFoundException; | |
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException; | |
import org.eclipse.epsilon.eol.exceptions.models.EolNotInstantiableModelElementTypeException; | |
import org.eclipse.epsilon.eol.execute.context.Variable; | |
import org.eclipse.epsilon.eol.execute.control.DefaultExecutionController; | |
import org.eclipse.epsilon.eol.execute.introspection.IPropertyGetter; | |
import org.eclipse.epsilon.eol.execute.introspection.IPropertySetter; | |
import org.eclipse.epsilon.eol.types.EolAnyType; | |
import org.eclipse.epsilon.eol.types.EolSequence; | |
import org.eclipse.hawk.core.IModelIndexer; | |
import org.eclipse.hawk.core.IStateListener.HawkState; | |
import org.eclipse.hawk.core.graph.IGraphDatabase; | |
import org.eclipse.hawk.core.graph.IGraphEdge; | |
import org.eclipse.hawk.core.graph.IGraphIterable; | |
import org.eclipse.hawk.core.graph.IGraphNode; | |
import org.eclipse.hawk.core.graph.IGraphNodeIndex; | |
import org.eclipse.hawk.core.graph.IGraphNodeReference; | |
import org.eclipse.hawk.core.graph.IGraphTransaction; | |
import org.eclipse.hawk.core.graph.timeaware.ITimeAwareGraphNodeIndex; | |
import org.eclipse.hawk.core.query.IQueryEngine; | |
import org.eclipse.hawk.core.query.InvalidQueryException; | |
import org.eclipse.hawk.core.query.QueryExecutionException; | |
import org.eclipse.hawk.core.util.Utils; | |
import org.eclipse.hawk.epsilon.emc.contextful.CEOLQueryEngine; | |
import org.eclipse.hawk.epsilon.emc.optimisation.OptimisableCollection; | |
import org.eclipse.hawk.epsilon.emc.pgetters.GraphPropertyGetter; | |
import org.eclipse.hawk.epsilon.emc.tracking.AccessListener; | |
import org.eclipse.hawk.epsilon.emc.wrappers.FileNodeWrapper; | |
import org.eclipse.hawk.epsilon.emc.wrappers.MetamodelNodeWrapper; | |
import org.eclipse.hawk.epsilon.emc.wrappers.TypeNodeWrapper; | |
import org.eclipse.hawk.graph.FileNode; | |
import org.eclipse.hawk.graph.GraphWrapper; | |
import org.eclipse.hawk.graph.MetamodelNode; | |
import org.eclipse.hawk.graph.ModelElementNode; | |
import org.eclipse.hawk.graph.ProxyReferenceList; | |
import org.eclipse.hawk.graph.Slot; | |
import org.eclipse.hawk.graph.TypeNode; | |
import org.eclipse.hawk.graph.updater.DirtyDerivedFeaturesListener; | |
import org.eclipse.hawk.graph.updater.GraphModelBatchInjector; | |
import org.eclipse.hawk.graph.updater.GraphModelInserter; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
/** | |
* Exposes a Hawk instance as an Epsilon model and adds support for EOL queries to Hawk. | |
* Other Epsilon languages may inherit from this class, redefining the {@link #createModule()} | |
* method and being careful with derived attributes (if they support them at all). | |
* | |
* TODO: investigate how this class could be broken up into model + proper query engine. | |
* Right now the two seem to be deeply intertwined and separating them would break backwards | |
* compatibility. | |
*/ | |
public class EOLQueryEngine extends AbstractHawkModel implements IQueryEngine { | |
/** | |
* Weak ID-based reference to a node. In cases of heavy memory usage, it can GC the node | |
* itself and retrieve it again a later time. | |
* | |
* This class should only be instantiated from the query engine. Subclasses of EOLQueryEngine | |
* may want to provide their own wrapper subclasses. | |
*/ | |
public class GraphNodeWrapper implements IGraphNodeReference { | |
protected String id; | |
protected EOLQueryEngine containerModel; | |
protected WeakReference<IGraphNode> node; | |
protected GraphNodeWrapper(IGraphNode n, EOLQueryEngine containerModel) { | |
node = new WeakReference<IGraphNode>(n); | |
this.id = n.getId().toString(); | |
this.containerModel = containerModel; | |
} | |
@Override | |
public IGraphNode getNode() { | |
IGraphNode ret = node.get(); | |
if (ret == null) { | |
ret = containerModel.getBackend().getNodeById(id); | |
node = new WeakReference<IGraphNode>(ret); | |
} | |
return ret; | |
} | |
@Override | |
public String getId() { | |
return id; | |
} | |
@Override | |
public EOLQueryEngine getContainerModel() { | |
return containerModel; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (this == obj) | |
return true; | |
if (obj == null) | |
return false; | |
if (getClass() != obj.getClass()) | |
return false; | |
GraphNodeWrapper other = (GraphNodeWrapper) obj; | |
return Objects.equals(containerModel, other.containerModel) && Objects.equals(id, other.id); | |
} | |
public Object getFeature(String name) throws EolRuntimeException { | |
return containerModel.getPropertyGetter().invoke(this, name); | |
} | |
/** | |
* Returns true if this model element is contained directly or indirectly | |
* within the specified path at the specified repository. | |
*/ | |
public boolean isContainedWithin(String repository, String path) { | |
try (IGraphTransaction t = containerModel.getBackend().beginTransaction()) { | |
final ModelElementNode men = new ModelElementNode(getNode()); | |
return men.isContainedWithin(repository, path); | |
} catch (Exception e) { | |
LOGGER.error(e.getMessage(), e); | |
} | |
return false; | |
} | |
@Override | |
public String getTypeName() { | |
String type = ""; | |
try (IGraphTransaction t = containerModel.getBackend().beginTransaction()) { | |
IGraphNode n = getNode(); | |
final Iterator<IGraphEdge> itTypeNode = n.getOutgoingWithType(ModelElementNode.EDGE_LABEL_OFTYPE).iterator(); | |
if (itTypeNode.hasNext()) { | |
final IGraphNode typeNode = itTypeNode.next().getEndNode(); | |
type = typeNode.getProperty(IModelIndexer.IDENTIFIER_PROPERTY).toString(); | |
} else { | |
LOGGER.error("No type node found for node {}", n); | |
} | |
t.success(); | |
} catch (Exception e) { | |
LOGGER.error(e.getMessage(), e); | |
} | |
return type; | |
} | |
@Override | |
public String toString() { | |
String info = ""; | |
try (IGraphTransaction t = containerModel.getBackend().beginTransaction()) { | |
info += "type:" + getTypeName(); | |
info += ""; | |
t.success(); | |
} catch (Exception e) { | |
LOGGER.error(e.getMessage(), e); | |
} | |
return "GNW|id:" + id + "|" + (info.equals("") ? "[no meta-info]" : info) + ""; | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(containerModel, id); | |
} | |
} | |
protected class IGraphIterableCollection implements Collection<GraphNodeWrapper> { | |
private final IGraphIterable<? extends IGraphNode> iterableNodes; | |
protected IGraphIterableCollection(IGraphIterable<? extends IGraphNode> iterableNodes) { | |
this.iterableNodes = iterableNodes; | |
} | |
@Override | |
public int size() { | |
return iterableNodes.size(); | |
} | |
@Override | |
public boolean isEmpty() { | |
return iterableNodes.size() > 0; | |
} | |
@Override | |
public boolean contains(Object o) { | |
for (final Iterator<GraphNodeWrapper> itN = iterator(); itN.hasNext(); ) { | |
if (itN.next().equals(o)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
@Override | |
public Iterator<GraphNodeWrapper> iterator() { | |
final Iterator<? extends IGraphNode> itNodes = iterableNodes.iterator(); | |
return new Iterator<GraphNodeWrapper>() { | |
@Override | |
public boolean hasNext() { | |
return itNodes.hasNext(); | |
} | |
@Override | |
public GraphNodeWrapper next() { | |
return wrap(itNodes.next()); | |
} | |
}; | |
} | |
@Override | |
public Object[] toArray() { | |
return toArray(new Object[size()]); | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
public <T> T[] toArray(T[] a) { | |
if (a.length < size()) { | |
final T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), size()); | |
return toArray(result); | |
} | |
int i = 0; | |
for (final Iterator<GraphNodeWrapper> itN = iterator(); itN.hasNext(); i++) { | |
final GraphNodeWrapper n = itN.next(); | |
a[i] = (T) n; | |
} | |
while (i < a.length) { | |
a[i++] = null; | |
} | |
return a; | |
} | |
@Override | |
public boolean add(GraphNodeWrapper e) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean remove(Object o) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean containsAll(Collection<?> c) { | |
for (Object e : c) { | |
if (!contains(e)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
@Override | |
public boolean addAll(Collection<? extends GraphNodeWrapper> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean removeAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean retainAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public void clear() { | |
throw new UnsupportedOperationException(); | |
} | |
} | |
/** | |
* Allows a query to be cancelled by the user according to an arbitrary binary flag. | |
* This could be used e.g. from an Eclipse job listener. | |
*/ | |
public static class SettableExecutionController extends DefaultExecutionController { | |
private volatile boolean isTerminated = false; | |
@Override | |
public boolean isTerminated() { | |
return isTerminated; | |
} | |
public void setTerminated(boolean terminated) { | |
this.isTerminated = terminated; | |
} | |
} | |
interface Function3<T, U, V> { | |
V apply(T t, U u); | |
} | |
public static final String TYPE = "org.eclipse.hawk.epsilon.emc.EOLQueryEngine"; | |
private static final Logger LOGGER = LoggerFactory.getLogger(EOLQueryEngine.class); | |
private static final String MMURI_TYPE_SEPARATOR = "::"; | |
private static final String ANY_TYPE = new EolAnyType().getName(); | |
/* TODO: these two should not have to be static.*/ | |
protected IModelIndexer indexer = null; | |
protected IGraphDatabase graph = null; | |
protected IGraphNodeIndex metamodeldictionary; | |
protected Set<String> defaultNamespaces = null; | |
protected GraphPropertyGetter propertyGetter; | |
/** Speeds up repeated queries for type nodes. */ | |
private Map<String, List<IGraphNode>> typeNodesCache = new WeakHashMap<>(); | |
/** Do not use OptimisedCollection unless we can benefit from it. */ | |
private boolean useOptimisableCollection; | |
/** | |
* Returns all of the contents of the database in the form of lightweight | |
* {@link GraphNodeWrapper} objects. | |
*/ | |
@Override | |
public Collection<?> allContents() { | |
final IGraphIterable<? extends IGraphNode> iterableNodes = graph.allNodes(ModelElementNode.OBJECT_VERTEX_LABEL); | |
final Collection<GraphNodeWrapper> allContents = new IGraphIterableCollection(iterableNodes); | |
broadcastAllOfXAccess(allContents); | |
return allContents; | |
} | |
@Override | |
public Object createInstance(String metaClassName) | |
throws EolModelElementTypeNotFoundException, EolNotInstantiableModelElementTypeException { | |
throw new EolNotInstantiableModelElementTypeException("Hawk Model Index", metaClassName); | |
} | |
@Override | |
public void deleteElement(Object arg0) throws EolRuntimeException { | |
throw new EolRuntimeException("Hawk Model Index: this type of model cannot create/delete elements"); | |
} | |
public Collection<Object> getAllOf(String typeName, final String typeorkind) throws EolModelElementTypeNotFoundException, EolInternalException { | |
try { | |
final List<IGraphNode> typeNodes = getTypeNodes(typeName); | |
if (typeNodes.size() == 1) { | |
return getAllOf(typeNodes.get(0), typeorkind); | |
} | |
} catch (Exception e) { | |
throw new EolInternalException(e); | |
} | |
throw new EolModelElementTypeNotFoundException(this.getName(), typeName); | |
} | |
/** | |
* Convenience version of | |
* {@link #getAllOf(String, String, String, String, String)} that looks in all | |
* repositories. | |
*/ | |
public Collection<GraphNodeWrapper> getAllOf(final String metamodelURI, final String typeName, final String filePatterns) | |
throws EolInternalException, EolModelElementTypeNotFoundException { | |
return getAllOf(metamodelURI, typeName, "*", filePatterns); | |
} | |
/** | |
* Finds all the instances of a certain type (including subtypes) contained in | |
* the specified repository and file patterns. This version assumes we will provide | |
* a limited number of files and that these will be small - we start from the files. | |
* | |
* @param metamodelURI | |
* URI of the metamodel, e.g. | |
* <code>http://eclipse.org/example</code>. | |
* @param typeName | |
* Name of the type within the metamodel, e.g. | |
* <code>Subsystem</code>. | |
* @param repoPattern | |
* Pattern of the repository or repositories to use, e.g. | |
* <code>file:/*</code> or <code>project:/resource</code>. | |
* @param filePatterns | |
* Comma-separated list of file patterns, such as | |
* <code>/a/b/c.xmi</code> or <code>/d/*</code>. | |
* If URI-invalid characters are found in the pattern, | |
* they will be URI-encoded. | |
* @throws EolInternalException | |
* Error while retrieving the type/file nodes. | |
* @throws EolModelElementTypeNotFoundException | |
* Could not find the specified type. | |
*/ | |
public Collection<GraphNodeWrapper> getAllOf(final String metamodelURI, final String typeName, final String repoPattern, final String filePatterns) | |
throws EolInternalException, EolModelElementTypeNotFoundException | |
{ | |
try { | |
List<IGraphNode> typeNodes = getTypeNodes(metamodelURI, typeName); | |
if (typeNodes.size() == 1) { | |
final TypeNode targetTypeNode = new TypeNode(typeNodes.get(0)); | |
final List<GraphNodeWrapper> results = new ArrayList<>(); | |
final Set<FileNode> fileNodes = new GraphWrapper(graph).getFileNodes( | |
Collections.singleton(repoPattern), | |
Arrays.asList(filePatterns.split(","))); | |
for (FileNode fn : fileNodes) { | |
for (ModelElementNode me : fn.getModelElements()) { | |
if (me.isOfKind(targetTypeNode)) { | |
results.add(wrap(me.getNode())); | |
} | |
} | |
} | |
return results; | |
} | |
} catch (Exception e) { | |
throw new EolInternalException(e); | |
} | |
throw new EolModelElementTypeNotFoundException(this.getName(), typeName); | |
} | |
public Collection<Object> getAllOf(IGraphNode typeNode, final String typeorkind) { | |
Collection<Object> nodes = createAllOfCollection(typeNode); | |
for (IGraphEdge n : typeNode.getIncomingWithType(typeorkind)) { | |
nodes.add(wrap(n.getStartNode())); | |
} | |
broadcastAllOfXAccess(nodes); | |
return nodes; | |
} | |
protected Collection<Object> createAllOfCollection(IGraphNode typeNode) { | |
Collection<Object> nodes = useOptimisableCollection | |
? new OptimisableCollection(this, wrap(typeNode)) : new EolSequence<>(); | |
return nodes; | |
} | |
public List<IGraphNode> getTypeNodes(String typeName) { | |
List<IGraphNode> typeNodes = typeNodesCache.get(typeName); | |
if (typeNodes == null) { | |
typeNodes = computeTypeNodes(typeName); | |
typeNodesCache.put(typeName, typeNodes); | |
} | |
return typeNodes; | |
} | |
protected List<IGraphNode> computeTypeNodes(String typeName) { | |
final int idxColon = typeName.lastIndexOf(MMURI_TYPE_SEPARATOR); | |
if (idxColon != -1) { | |
final String epackage = typeName.substring(0, idxColon); | |
final String type = typeName.substring(idxColon + MMURI_TYPE_SEPARATOR.length()); | |
return getTypeNodes(epackage, type); | |
} else { | |
final IGraphIterable<? extends IGraphNode> allMetamodels = metamodeldictionary.query("id", "*"); | |
final List<IGraphNode> candidates = new LinkedList<IGraphNode>(); | |
for (final Iterator<? extends IGraphNode> packs = allMetamodels.iterator(); packs.hasNext(); ) { | |
IGraphNode pack = packs.next(); | |
for (IGraphEdge n : pack.getIncomingWithType("epackage")) { | |
final IGraphNode othernode = n.getStartNode(); | |
final Object id = othernode.getProperty(IModelIndexer.IDENTIFIER_PROPERTY); | |
if (id.equals(typeName)) { | |
candidates.add(othernode); | |
} | |
} | |
} | |
if (candidates.size() == 1) { | |
return candidates; | |
} else if (candidates.size() > 1) { | |
// use default namespaces to limit types | |
for (Iterator<IGraphNode> it = candidates.iterator(); it.hasNext();) { | |
IGraphNode n = it.next(); | |
String metamodel = new TypeNode(n).getMetamodelURI(); | |
if (defaultNamespaces != null && !defaultNamespaces.isEmpty() && !defaultNamespaces.contains(metamodel)) { | |
it.remove(); | |
} | |
} | |
return candidates; | |
} | |
} | |
return Collections.emptyList(); | |
} | |
protected List<IGraphNode> getTypeNodes(String mmURI, String type) { | |
Iterator<? extends IGraphNode> itPack = metamodeldictionary.get("id", mmURI).iterator(); | |
if (!itPack.hasNext()) { | |
throw new NoSuchElementException("Could not find the metamodel node for " + mmURI); | |
} | |
IGraphNode pack = itPack.next(); | |
for (IGraphEdge r : pack.getIncomingWithType("epackage")) { | |
IGraphNode othernode = r.getStartNode(); | |
if (othernode.getProperty(IModelIndexer.IDENTIFIER_PROPERTY).equals(type)) { | |
return Collections.singletonList(othernode); | |
} | |
} | |
return Collections.emptyList(); | |
} | |
protected void broadcastAllOfXAccess(Iterable<?> ret) { | |
/* | |
* TODO can optimise by not keeping all the nodes of type/kind but the | |
* concept of type/kind and only update the attr if a new node is added | |
* or one is removed. | |
*/ | |
if (((GraphPropertyGetter) propertyGetter).getBroadcastStatus()) { | |
for (Object n : ret) { | |
final AccessListener accessListener = ((GraphPropertyGetter) propertyGetter).getAccessListener(); | |
final String sID = ((GraphNodeWrapper) n).getId() + ""; | |
accessListener.accessed(sID, "property_unused_type_or_kind"); | |
} | |
} | |
} | |
@Override | |
public Object getElementById(String id) { | |
IGraphNode ret = graph.getNodeById(id); | |
return ret == null ? null : wrap(ret); | |
} | |
@Override | |
public String getElementId(Object arg0) { | |
if (arg0 instanceof GraphNodeWrapper) | |
return ((GraphNodeWrapper) arg0).getId() + ""; | |
else | |
return null; | |
} | |
@Override | |
public String getTypeNameOf(Object arg0) { | |
String ret = null; | |
try { | |
Object type = getTypeOf(arg0); | |
IGraphNode typeNode = ((GraphNodeWrapper) type).getNode(); | |
ret = typeNode.getProperty(IModelIndexer.IDENTIFIER_PROPERTY).toString(); | |
} catch (Exception e) { | |
LOGGER.error(e.getMessage(), e); | |
} | |
return ret; | |
} | |
@Override | |
public TypeNodeWrapper getTypeOf(Object arg0) { | |
IGraphNode objectNode = ((GraphNodeWrapper) arg0).getNode(); | |
TypeNode typeNode = new ModelElementNode(objectNode).getTypeNode(); | |
if (typeNode != null) { | |
return new TypeNodeWrapper(typeNode, this); | |
} else { | |
return null; | |
} | |
} | |
@Override | |
public FileNodeWrapper getFileOf(Object arg0) { | |
IGraphNode objectNode = ((GraphNodeWrapper) arg0).getNode(); | |
FileNode fileNode = new ModelElementNode(objectNode).getFileNode(); | |
if (fileNode != null) { | |
return new FileNodeWrapper(fileNode, this); | |
} else { | |
return null; | |
} | |
} | |
@Override | |
public List<FileNodeWrapper> getFilesOf(Object arg0) { | |
List<FileNodeWrapper> ret = new ArrayList<>(); | |
IGraphNode objectNode = ((GraphNodeWrapper) arg0).getNode(); | |
for (FileNode fn : new ModelElementNode(objectNode).getFileNodes()) { | |
ret.add(new FileNodeWrapper(fn, this)); | |
} | |
return ret; | |
} | |
@Override | |
public boolean hasType(String type) { | |
return getTypeNodes(type).size() == 1; | |
} | |
@Override | |
public boolean isModelElement(Object arg0) { | |
return arg0 instanceof GraphNodeWrapper; | |
} | |
@Override | |
public void load() throws EolModelLoadingException { | |
if (graph != null) | |
load((IModelIndexer) null); | |
else | |
throw new EolModelLoadingException(new Exception("load called with no graph store initialized"), this); | |
} | |
public void load(IModelIndexer m) throws EolModelLoadingException { | |
if (m != null) { | |
indexer = m; | |
graph = m.getGraph(); | |
aliases.add(m.getName()); | |
} | |
if (name == null) { | |
name = "Model"; | |
} | |
if (propertyGetter == null || propertyGetter.getGraph() != graph) { | |
propertyGetter = createContextlessPropertyGetter(); | |
} | |
if (graph != null) { | |
try (IGraphTransaction tx = graph.beginTransaction()) { | |
metamodeldictionary = graph.getMetamodelIndex(); | |
useOptimisableCollection = !indexer.getIndexedAttributes().isEmpty() || !indexer.getDerivedAttributes().isEmpty(); | |
tx.success(); | |
} catch (Exception e) { | |
LOGGER.error("Could not retrieve the metamodel index", e); | |
} | |
} else { | |
throw new EolModelLoadingException(new Exception("Attempt to load a model from an invalid graph: " + graph), this); | |
} | |
} | |
@Override | |
public boolean owns(Object arg0) { | |
if (arg0 instanceof GraphNodeWrapper) { | |
final GraphNodeWrapper gnw = (GraphNodeWrapper) arg0; | |
if (gnw.getContainerModel() == null || gnw.getContainerModel() == this) { | |
return true; | |
} else { | |
LOGGER.warn("owns failed on {} with getContainerModel(): {} and 'this': {}", arg0, gnw.getContainerModel(), this); | |
} | |
} | |
return false; | |
} | |
@Override | |
public boolean isOfKind(Object instance, String metaClass) throws EolModelElementTypeNotFoundException { | |
return isOf(instance, metaClass, ModelElementNode::isOfKind); | |
} | |
@Override | |
public boolean isOfType(Object instance, String metaClass) throws EolModelElementTypeNotFoundException { | |
return isOf(instance, metaClass, ModelElementNode::isOfType); | |
} | |
private boolean isOf(Object instance, String metaClass, Function3<ModelElementNode, IGraphNode, Boolean> isOfCheck) { | |
if (!(instance instanceof GraphNodeWrapper)) { | |
return false; | |
} | |
if (ANY_TYPE.equals(metaClass)) { | |
// Needed to support the 'Any' supertype in Epsilon (useful in EPL) | |
return true; | |
} | |
final List<IGraphNode> typeNodes = getTypeNodes(metaClass); | |
if (typeNodes.isEmpty()) { | |
return false; | |
} else { | |
final GraphNodeWrapper gnw = (GraphNodeWrapper) instance; | |
final ModelElementNode men = new ModelElementNode(gnw.getNode()); | |
return isOfCheck.apply(men, typeNodes.get(0)); | |
} | |
} | |
@Override | |
public boolean knowsAboutProperty(Object instance, String property) { | |
if (!owns(instance)) | |
return false; | |
if (instance instanceof GraphNodeWrapper) | |
return true; | |
return false; | |
} | |
@Override | |
public IPropertyGetter getPropertyGetter() { | |
if (propertyGetter == null) { | |
LOGGER.warn("null property getter, was load() called?"); | |
} | |
return propertyGetter; | |
} | |
@Override | |
public IPropertySetter getPropertySetter() { | |
return null; | |
} | |
public IGraphDatabase getBackend() { | |
return graph; | |
} | |
public List<ProxyReferenceList> getProxies() throws Exception { | |
return ProxyReferenceList.getLists(graph); | |
} | |
public List<ProxyReferenceList> getProxies(String repositoryPrefix) throws Exception { | |
return ProxyReferenceList.getLists(graph, repositoryPrefix); | |
} | |
public List<TypeNodeWrapper> getTypes() { | |
final List<TypeNodeWrapper> nodes = new ArrayList<>(); | |
for (IGraphNode n : graph.getMetamodelIndex().query("*", "*")) { | |
final MetamodelNode pn = new MetamodelNode(n); | |
for (TypeNode tn : pn.getTypes()) { | |
nodes.add(new TypeNodeWrapper(tn, this)); | |
} | |
} | |
return nodes; | |
} | |
public List<MetamodelNodeWrapper> getMetamodels() { | |
final List<MetamodelNodeWrapper> nodes = new ArrayList<>(); | |
for (IGraphNode n : graph.getMetamodelIndex().query("*", "*")) { | |
final MetamodelNode pn = new MetamodelNode(n); | |
nodes.add(new MetamodelNodeWrapper(pn, this)); | |
} | |
return nodes; | |
} | |
/** | |
* Returns the collection of all the files indexed in the graph. | |
*/ | |
public Set<FileNodeWrapper> getFiles() { | |
Set<FileNodeWrapper> allFNW = new HashSet<>(); | |
for (IGraphNode n : graph.allNodes(FileNode.FILE_NODE_LABEL)) { | |
allFNW.add(new FileNodeWrapper(new FileNode(n), this)); | |
} | |
return allFNW; | |
} | |
// deriving attributes | |
@Override | |
public AccessListener calculateDerivedAttributes(IModelIndexer m, Iterable<IGraphNode> nodes) { | |
final boolean enableDebug = false; | |
indexer = m; | |
graph = m.getGraph(); | |
if (propertyGetter == null) { | |
propertyGetter = createContextlessPropertyGetter(); | |
} | |
try { | |
load(m); | |
} catch (EolModelLoadingException e2) { | |
e2.printStackTrace(); | |
} | |
// listen to accesses | |
GraphPropertyGetter pg = null; | |
if (!enableDebug) { | |
pg = (GraphPropertyGetter) getPropertyGetter(); | |
pg.setBroadcastAccess(true); | |
} | |
// Compute the derived attributes | |
final Map<String, EolModule> cachedModules = new HashMap<String, EolModule>(); | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
for (IGraphNode n : nodes) { | |
final String attrType = n.getProperty(GraphModelInserter.DERIVED_ATTR_TYPE) + ""; | |
if (Slot.ATTR_TYPE_TIMEANNOTATION.equals(attrType)) { | |
calculateTimelineAnnotation(cachedModules, n); | |
} else { | |
calculateDerivedAttributes(cachedModules, n); | |
} | |
} | |
t.success(); | |
} catch (Exception e) { | |
LOGGER.error("Failed to compute the derived attributes", e); | |
} | |
if (!enableDebug) { | |
pg.setBroadcastAccess(false); | |
return pg.getAccessListener(); | |
} | |
return null; | |
} | |
private void calculateTimelineAnnotation(Map<String, EolModule> cachedModules, IGraphNode n) { | |
// TODO Auto-generated method stub | |
final String derivationLogic = n.getProperty(GraphModelInserter.DERIVED_ATTR_LOGIC) + ""; | |
final String name = n.getIncoming().iterator().next().getType(); | |
try { | |
Object value = new DeriveFeature().deriveTimelineAnnotation(cachedModules, indexer, n, this, derivationLogic); | |
if (value instanceof Boolean && ((Boolean)value).booleanValue()) { | |
final IGraphNode elementNode = n.getIncoming().iterator().next().getStartNode(); | |
final String idxName = n.getProperty(GraphModelInserter.DERIVED_IDXNAME_NODEPROP).toString(); | |
final IGraphNodeIndex idxNodeByDerivedValue = graph.getOrCreateNodeIndex(idxName); | |
if (idxNodeByDerivedValue instanceof ITimeAwareGraphNodeIndex) { | |
ITimeAwareGraphNodeIndex taNI = (ITimeAwareGraphNodeIndex) idxNodeByDerivedValue; | |
taNI.annotate(elementNode, name); | |
} else { | |
idxNodeByDerivedValue.remove(elementNode, name, null); | |
idxNodeByDerivedValue.add(elementNode, name, true); | |
} | |
// Note: this is needed in order to mark the node as "dirty" to some time-aware backends. | |
elementNode.setProperty(GraphModelInserter.LAST_DERIVED_TSTAMP_NODEPROP, System.nanoTime()); | |
} | |
} catch (Exception e) { | |
LOGGER.error("Error while deriving timeline annotation " + derivationLogic, e); | |
} | |
} | |
protected GraphPropertyGetter createContextlessPropertyGetter() { | |
return new GraphPropertyGetter(graph, this); | |
} | |
protected void calculateDerivedAttributes(Map<String, EolModule> cachedModules, IGraphNode n) { | |
for (String propKey : n.getPropertyKeys()) { | |
String propValue = n.getProperty(propKey).toString(); | |
if (propValue.startsWith(DirtyDerivedFeaturesListener.NOT_YET_DERIVED_PREFIX)) { | |
Object derived = "DERIVATION_EXCEPTION"; | |
try { | |
derived = new DeriveFeature().deriveFeature(cachedModules, indexer, n, this, propValue); | |
} catch (Exception e) { | |
LOGGER.error("Error while deriving feature " + propValue, e); | |
} | |
// Unset the current value (if there is any) | |
final String derivedEdgeLabel = ModelElementNode.DERIVED_EDGE_PREFIX + propKey; | |
for (IGraphEdge edge : n.getOutgoingWithType(derivedEdgeLabel)) { | |
LOGGER.debug("Clearing edge {}", edge.getType()); | |
edge.delete(); | |
} | |
n.removeProperty(propKey); | |
// Set the new value | |
if (derived instanceof Object[] && ((Object[])derived).length > 0 && ((Object[])derived)[0] instanceof GraphNodeWrapper) { | |
GraphNodeWrapper[] nodes = (GraphNodeWrapper[])derived; | |
// Replace existing edges with new ones | |
for (GraphNodeWrapper gw : nodes) { | |
graph.createRelationship(n, gw.getNode(), derivedEdgeLabel); | |
} | |
} else if (derived instanceof GraphNodeWrapper) { | |
GraphNodeWrapper gw = (GraphNodeWrapper) derived; | |
graph.createRelationship(n, gw.getNode(), derivedEdgeLabel); | |
} else if (derived != null) { | |
n.setProperty(propKey, derived); | |
} else { | |
n.setProperty(propKey, new String[0]); | |
} | |
final IGraphNode elementNode = n.getIncoming().iterator().next().getStartNode(); | |
final String idxName = n.getProperty(GraphModelInserter.DERIVED_IDXNAME_NODEPROP).toString(); | |
final IGraphNodeIndex idxNodeByDerivedValue = graph.getOrCreateNodeIndex(idxName); | |
// flatten multi-valued derived features for indexing | |
if (derived != null) { | |
if (derived.getClass().getComponentType() != null || derived instanceof Collection<?>) { | |
derived = new Utils().toString(derived); | |
} | |
// TODO: need to test how this works with derived edges | |
idxNodeByDerivedValue.remove(elementNode, propKey, null); | |
idxNodeByDerivedValue.add(elementNode, propKey, derived); | |
// Note: this is needed in order to mark the node as "dirty" to some time-aware backends. | |
elementNode.setProperty(GraphModelInserter.LAST_DERIVED_TSTAMP_NODEPROP, System.nanoTime()); | |
} | |
} | |
} | |
IGraphNodeIndex derivedProxyDictionary = graph.getOrCreateNodeIndex(GraphModelBatchInjector.DERIVED_PROXY_DICT_NAME); | |
derivedProxyDictionary.remove(n); | |
} | |
@Override | |
public String getType() { | |
/* | |
* This *must* be overridden in subclasses, otherwise Hawk may pick the wrong | |
* query engine! It happened in GH issue #78. | |
*/ | |
return TYPE; | |
} | |
@Override | |
public List<String> validate(String derivationlogic) { | |
final IEolModule module = createModule(); | |
final List<String> ret = new LinkedList<>(); | |
try { | |
module.parse(derivationlogic); | |
for (ParseProblem p : module.getParseProblems()) { | |
ret.add(p.toString()); | |
} | |
} catch (Exception e) { | |
LOGGER.error("Error while parsing EOL", e); | |
} | |
return ret; | |
} | |
@Override | |
public Object query(IModelIndexer m, String query, Map<String, Object> context) | |
throws InvalidQueryException, QueryExecutionException { | |
/* | |
* Check if we're in the right state: we should not use {@link | |
* IModelIndexer#waitFor} here, as that would introduce unwanted | |
* synchronisation (reducing peak throughput for some cases). | |
*/ | |
final HawkState currentState = m.getCompositeStateListener().getCurrentState(); | |
if (currentState != HawkState.RUNNING) { | |
throw new QueryExecutionException( | |
String.format("Cannot run the query, as the indexer is not in the RUNNING state: it is %s instead.", | |
currentState)); | |
} | |
if (context != null && (context.containsKey(PROPERTY_REPOSITORYCONTEXT) || context.containsKey(PROPERTY_FILECONTEXT) || context.containsKey(PROPERTY_SUBTREECONTEXT))) { | |
return contextfulQuery(m, query, context); | |
} else { | |
return contextlessQuery(m, query, context); | |
} | |
} | |
protected Object contextlessQuery(IModelIndexer m, String query, Map<String, Object> context) | |
throws QueryExecutionException, InvalidQueryException { | |
String defaultnamespaces = null; | |
if (context != null) | |
defaultnamespaces = (String) context.get(PROPERTY_DEFAULTNAMESPACES); | |
/* | |
* We need to always create a new engine for every query: reusing the | |
* same engine would be thread-unsafe. | |
*/ | |
final EOLQueryEngine q = new EOLQueryEngine(); | |
try { | |
q.load(m); | |
q.setDefaultNamespaces(defaultnamespaces); | |
} catch (EolModelLoadingException e) { | |
throw new QueryExecutionException("Loading of EOLQueryEngine failed"); | |
} | |
final IEolModule module = createModule(); | |
parseQuery(query, context, q, module); | |
return q.runQuery(module); | |
} | |
protected Object contextfulQuery(IModelIndexer m, String query, Map<String, Object> context) | |
throws QueryExecutionException, InvalidQueryException { | |
CEOLQueryEngine q = new CEOLQueryEngine(); | |
try { | |
q.load(m); | |
q.setContext(context); | |
} catch (EolModelLoadingException e) { | |
throw new QueryExecutionException("Loading of EOLQueryEngine failed"); | |
} | |
LOGGER.debug("Graph path: {}", graph.getPath()); | |
final IEolModule module = createModule(); | |
parseQuery(query, context, q, module); | |
return q.runQuery(module); | |
} | |
// IQueryEngine part ////////////////////////////////////////////////////// | |
/** | |
* Query engines that add support for other Epsilon languages should redefine this method. | |
*/ | |
protected IEolModule createModule() { | |
return new EolModule(); | |
} | |
protected void parseQuery(String query, Map<String, Object> context, final EOLQueryEngine model, | |
final IEolModule module) throws InvalidQueryException { | |
try { | |
module.parse(query); | |
if (!module.getParseProblems().isEmpty()) { | |
StringBuilder sb = new StringBuilder("Query failed to parse correctly:"); | |
for (ParseProblem problem : module.getParseProblems()) { | |
sb.append("\n"); | |
sb.append(problem.toString()); | |
} | |
throw new InvalidQueryException(sb.toString()); | |
} | |
} catch (Exception ex) { | |
throw new InvalidQueryException(ex); | |
} | |
module.getContext().getModelRepository().addModel(model); | |
addQueryArguments(context, module); | |
if (context != null && context.containsKey(IQueryEngine.PROPERTY_CANCEL_CONSUMER)) { | |
@SuppressWarnings("unchecked") | |
final Consumer<Runnable> cancelProvider = (Consumer<Runnable>) context.get(IQueryEngine.PROPERTY_CANCEL_CONSUMER); | |
final SettableExecutionController controller = new SettableExecutionController(); | |
module.getContext().getExecutorFactory().setExecutionController(controller); | |
cancelProvider.accept(() -> { | |
controller.setTerminated(true); | |
}); | |
} | |
} | |
protected Object runQuery(final IEolModule module) throws QueryExecutionException { | |
Object ret = null; | |
try (IGraphTransaction tx = graph.beginTransaction()) { | |
ret = module.execute(); | |
tx.success(); | |
} catch (EolUndefinedVariableException ex) { | |
// Provide more details than Epsilon about ambiguous intra-model type references | |
try (IGraphTransaction tx = graph.beginTransaction()) { | |
/* | |
* Use the same EOLQueryEngine as in the model - if using a separate object | |
* (e.g. the time-aware one) this EOLQueryEngine might not be loaded. | |
*/ | |
final EOLQueryEngine eolQuery = (EOLQueryEngine) module.getContext().getModelRepository().getModels().get(0); | |
final List<IGraphNode> typeNodes = eolQuery.getTypeNodes(ex.getVariableName()); | |
final StringBuilder sb = new StringBuilder("Ambiguous type reference '" + ex.getVariableName() + "' across these metamodels:\n"); | |
for (IGraphNode typeNode : typeNodes) { | |
sb.append("\n* "); | |
sb.append(new TypeNode(typeNode).getMetamodelURI()); | |
} | |
sb.append("\n\nSpecify the desired one in the default namespaces to resolve the ambiguity."); | |
tx.success(); | |
if (typeNodes.size() > 1) { | |
throw new QueryExecutionException(sb.toString()); | |
} else { | |
throw new QueryExecutionException(ex); | |
} | |
} catch (QueryExecutionException e) { | |
throw e; | |
} catch (Exception e) { | |
throw new QueryExecutionException(e); | |
} | |
} catch (Exception e) { | |
throw new QueryExecutionException(e); | |
} | |
return ret; | |
} | |
@SuppressWarnings("unchecked") | |
protected void addQueryArguments(Map<String, Object> context, final IEolModule module) { | |
if (context != null) { | |
final Map<String, Object> args = (Map<String, Object>) context.get(PROPERTY_ARGUMENTS); | |
if (args != null) { | |
for (Entry<String, Object> entry : args.entrySet()) { | |
module.getContext().getFrameStack().putGlobal(new Variable(entry.getKey(), entry.getValue(), null)); | |
} | |
} | |
} | |
} | |
@Override | |
public void setDefaultNamespaces(String namespaces) { | |
// set default packages if applicable | |
try { | |
defaultNamespaces = new HashSet<String>(); | |
if (namespaces != null && !namespaces.trim().equals("")) { | |
String[] eps = ((String) namespaces).split(","); | |
for (String s : eps) { | |
defaultNamespaces.add(s.trim()); | |
} | |
} | |
} catch (Throwable t) { | |
LOGGER.error("Setting default namespaces failed, malformed property: " + namespaces, t); | |
} | |
} | |
@Override | |
public String getHumanReadableName() { | |
return "EOL Query Engine"; | |
} | |
/** | |
* Wraps the provided node for its use in EOL. | |
* | |
* TODO change return type to {@link IGraphNodeReference} | |
*/ | |
public GraphNodeWrapper wrap(IGraphNode node) { | |
return new GraphNodeWrapper(node, this); | |
} | |
} |