A common task when using models is to perform a query to find elements fulfilling some criteria. RMP offers many ways to perform such queries: using UML Helper classes, using the index, using EMF's reflection APIs, or using EMF Query.
UMLModeler
gives access to simple utilities to find objects and diagrams by name and by ID.
IUMLHelper
is used to find UML model elements and
IUMLDiagramHelper
is used to find UML diagrams.
The ID of an object can be retrieved using EMF's
Resource.getURIFragment()
method. The following example gets the ID of the selected elements and finds them back using the API.
public void plugletmain(String[] args) {
try {
UMLModeler.getEditingDomain().runExclusive(new Runnable() {
public void run() {
try {
final List elements = UMLModeler.getUMLUIHelper().getSelectedElements();
// find each element
for (Iterator iter = elements.iterator(); iter.hasNext();) {
EObject eObject = (EObject) iter.next();
String id = eObject.eResource().getURIFragment(eObject);
if (eObject instanceof Element) {
Element element = (Element) eObject;
Element elementFound = UMLModeler.getUMLHelper().findElementById(element.getModel(),
id, new NullProgressMonitor());
out.println(elementFound == element);
}
}
} catch (InterruptedException e) {
out.println("The operation was interrupted"); //$NON-NLS-1$
}
}
});
} catch (InterruptedException e) {
out.println("The operation was interrupted"); //$NON-NLS-1$
}
}
These helper APIs are quick since they leverage the index feature presented below.
The DevOps Modeling Platform includes a powerful index to query models both in memory and on disk.
The infrastructure supports the indexing of instance models from arbitrary Ecore models. By default,
it indexes the references and the EClass of any EObject. The infrastructure can optionally
index addition EAttributes. Out of the box, the platform indexes UML and Notation models found in files with
the following extensions: .emx, .efx, .epx, .uml, .dnx, .tpx
. In addition to
the indexing references and EClass, the platform indexes the following EAttributes:
Using the index to perform a query is accomplished by calling the various find
methods of the
IIndexSearchManager
interface. Each of these methods requires an
IndexContext
that specifies the search scope as well as various search options.
The following example uses the index to query the referencers of the selected object and log them.
public void plugletmain(String[] args) {
/* Perform remaining work within a Runnable */
try {
UMLModeler.getEditingDomain().runExclusive(new Runnable() {
public void run() {
try {
// Get selection
final List elements = UMLModeler.getUMLUIHelper().getSelectedElements();
if (elements.size() == 0) {
out.println("No object selected.");
return;
}
// Find referencers
IndexContext indexContext = IndexContext.createWorkspaceContext(UMLModeler.getEditingDomain()
.getResourceSet());
indexContext.getOptions().put(IndexContext.SEARCH_UNLOADED_RESOURCES, Boolean.TRUE);
indexContext.getOptions().put(IndexContext.RESOLVE_PROXIES, Boolean.TRUE);
Map results = IIndexSearchManager.INSTANCE.findReferencingEObjects(indexContext, elements,
null, null, null);
// Log result
for (Iterator iter = results.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
logEObject((EObject) entry.getKey(), "References to ");
for (Iterator iterator = ((Collection) entry.getValue()).iterator(); iterator.hasNext();) {
EObject referencer = (EObject) iterator.next();
logEObject(referencer, "\t");
}
}
} catch (IndexException e) {
e.printStackTrace();
}
}
});
} catch (InterruptedException e) {
out.println("The operation was interrupted"); //$NON-NLS-1$
}
}
private void logEObject(EObject eObject, String prefix) {
EList adapterFactories = UMLModeler.getEditingDomain().getResourceSet().getAdapterFactories();
AdapterFactory adaptorFactory = EcoreUtil.getAdapterFactory(adapterFactories, IItemLabelProvider.class);
if (adaptorFactory != null) {
IItemLabelProvider itemLabelProvider = (IItemLabelProvider) adaptorFactory.adapt((Object) eObject,
IItemLabelProvider.class);
if (null != itemLabelProvider) {
out.println(prefix + itemLabelProvider.getText(eObject));
}
}
}
Because the index may return referencers whose Ecore model is unknown, we used a generic mechanism of EMF, AdapterFactories, to get a text string representing the referencers.
Model modifications not yet persisted are taken into account when performing index queries. The ResourceSet that is passed to the IndexContext's contructor determines the in-memory resource. Resource within the scope defined in the context but not loaded into the ResourceSet will be queried based on their on-disk representation.
Because the index searches within closed models, it may return proxies as opposed to resolve elements. The
IndexContext.RESOLVE_PROXIES
option forces these proxies to be resolved by the index
infrastructure. Resolving the proxy will load the model of the referencing object into the ResourceSet
passed to the IndexContext's contructor.
The index can query more than just reverse references, given an EObject or an URI, it can query :
More on the index framework can be found in the Searching EMF Index tutorial.
Sometimes, a query requires information that is not indexed. In such case, it is required to open the models and visit each element. EMF's reflection APIs come handy for such generic tasks. The following example get the first selected object, retrieve its model, and visits every elements to calculate its depth. It excludes diagrams.
public void plugletmain(String[] args) {
try {
UMLModeler.getEditingDomain().runExclusive(new Runnable() {
public void run() {
final List elements = UMLModeler.getUMLUIHelper().getSelectedElements();
if (elements.size() > 0) {
Object selectedObject = elements.get(0);
if (selectedObject instanceof Model) {
Model model = (Model) selectedObject;
out.println("Depth of " + model.getName() + " is " + calculateDepth(model));
return;
}
}
out.println("Select a model");
}
});
} catch (InterruptedException e) {
out.println("The operation was interrupted"); //$NON-NLS-1$
}
}
private int calculateDepth(EObject model) {
int maxDepth = 0;
for (Iterator iter = model.eContents().iterator(); iter.hasNext();) {
EObject eObject = (EObject) iter.next();
int childDepth = 0;
if (false == eObject instanceof Diagram) {
childDepth = calculateDepth(eObject);
}
maxDepth = Math.max(++childDepth, maxDepth);
}
return maxDepth;
}
While this approach is the most flexible and sometimes the only possible alternative, it is important to be
aware of its main limitation: it requires loading the model and each of its fragments, which does not scale
with large models. Traversing a model should never be performed without an explicit user action to perform
the query. Also, it should be interruptible using a ProgressMonitor or processed in a separate thread that
regularly calls org.eclipse.emf.transaction.TransactionalEditingDomain.yield()
.
An alternative to walking the model using EMF's reflection API is to leverage the query framework that is part of EMF Query. This framework proposes an elegant way of walking models by constructing conditions using an SQL-like 'syntax'. Using the EMF Query framework exhibits the same limitation found by walking a model using EMF reflection API.
The example that follows demonstrate how to use the EMF Query framework to find properties whose type is the selected element. The pluglet selects these properties in the Project Explorer.
/**
* Finds properties whose type is the selected object and select these
* properties.
*/
public void plugletmain(String[] args) {
final Set result = new HashSet();
try {
UMLModeler.getEditingDomain().runExclusive(new Runnable() {
public void run() {
final List elements = UMLModeler.getUMLUIHelper().getSelectedElements();
if (false == elements.isEmpty()) {
Object selectedObject = elements.get(0);
if (selectedObject instanceof Type) {
Type type = (Type) selectedObject;
EObjectCondition condition = new EObjectReferenceValueCondition(
new EObjectTypeRelationCondition(UMLPackage.Literals.PROPERTY),
UMLPackage.Literals.TYPED_ELEMENT__TYPE,
new EObjectInstanceCondition(type));
SELECT select = new SELECT(
SELECT.UNBOUNDED,
true,
new FROM(type.getModel()),
new WHERE(condition),
new NullProgressMonitor());
result.addAll(select.execute());
}
}
}
});
} catch (InterruptedException e) {
out.println("The operation was interrupted");
}
if (result.isEmpty()) {
out.println("Select a type");
} else {
selectedElementInPE(new ArrayList(result));
}
return;
}
/**
* Selects the specified elements in the project explorer. The selection
* request is asynchronous to ensure that any project explorer refreshes
* that were already queued in the Display thread's asynchronous runnables
* are processed before selection request.
*
* @param elements
* Elements to select
*/
private void selectedElementInPE(final List elements) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
try {
UMLModeler.getEditingDomain().runExclusive(new Runnable() {
public void run() {
UMLModeler.getUMLUIHelper().setSelectedElements("org.eclipse.ui.navigator.ProjectExplorer",
elements);
}
});
} catch (InterruptedException e) {
out.println("The operation was interrupted");
}
}
});
}
Executing the SELECT
statement will walk the elements starting from the object passed to the
FROM
clause and validate whether the
org.eclipse.emf.query.conditions.eobjects.structuralfeatures.EObjectReferenceValueCondition
condition is satisfied for each object encountered. The condition looks for objects of type Property
from the UML2 meta-model, whose Type
attribute inherited from Typed Element
references
the instance specified by our type
parameter; the selected object.
An aspect worth mentioning is that the EMF Query infrastructure is extremely flexible. This can be
appreciated from the
EObjectReferenceValueCondition
constructor.
Instead of simply receiving a type EClass and a value EObject as parameters, it requires conditions.
This allows the creation of complex queries by nesting conditions using operations such as
AND
and OR
.
UMLModeler provides a helper,
IQueryHelper
,
to simplify the execution of EMF Query. Its
executeQuery()
method will execute a query against the provided query root model element as well as the provided Condition.
Queries can also be take advantage of the OCL support offered through the EMF OCL component.
UMLModeler provides a helper to execute an OCL expression against a model,
IOclQueryHelper.evaluate()
;
as well as a helper to walk a model and execute a boolean OCL expression to filter content,
IOclQueryHelper.executeQueryUsingOclFilter()
. Both are accessible from
UMLModeler.getOclQueryHelper()
.
OCL can also be used in EMF Query through its
org.eclipse.emf.query.ocl.conditions.OCLConstraintCondition
class.
In the following example, we extend the previous EMF Query example by adding an extra OCL condition that stipulates
that the returned properties must be owned by classifier that specialize the selected element.
/**
* Finds properties whose type is the selected object, whose owner is a classifier
* that specializes the selected object, and selects them.
*/
public void plugletmain(String[] args) {
final Set result = new HashSet();
try {
UMLModeler.getEditingDomain().runExclusive(new Runnable() {
public void run() {
try {
final List elements = UMLModeler.getUMLUIHelper().getSelectedElements();
if (elements.size() > 0) {
Object selectedObject = elements.get(0);
if (selectedObject instanceof Classifier) {
Classifier classifier = (Classifier) selectedObject;
String oclExp =
"self.owner.oclIsKindOf(Classifier).and(self.owner.oclAsType(Classifier).allParents()->collect(c | c.qualifiedName )->includes('" + classifier.getQualifiedName() + "'))";
EObjectCondition condition =
new EObjectReferenceValueCondition(
new EObjectTypeRelationCondition(UMLPackage.Literals.PROPERTY),
UMLPackage.Literals.TYPED_ELEMENT__TYPE,
new EObjectInstanceCondition(classifier)).
AND(new OCLConstraintCondition(oclExp, UMLPackage.eINSTANCE.getProperty()));
Set queryResult = UMLModeler.getQueryHelper().executeQuery(
classifier.getModel(),
condition,
new NullProgressMonitor());
result.addAll(queryResult);
}
}
} catch (InterruptedException e) {
out.println("The operation was interrupted"); //$NON-NLS-1$
}
}
});
} catch (InterruptedException e) {
out.println("The operation was interrupted"); //$NON-NLS-1$
}
if (result.isEmpty()) {
out.println("Select a type");
} else {
selectedElementInPE(new ArrayList(result));
}
}
Notice here that we made use of the simpler
executeQuery()
API to execute our query. We also made use of the Condition.AND boolean operation to add our new OCL condition.