Querying Models

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.

Querying using UML Helpers

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.

Querying using the index

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.

Querying using EMF's reflection APIs

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().

Querying using EMF Query

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.

Querying using the Object Constraint Language (OCL)

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.


Legal notices