This Bugzilla instance is deprecated, and most Eclipse projects now use GitHub or Eclipse GitLab. Please see the deprecation plan for details.
Bug 424367 - [Model] EModelService - define a more generic 'find' API
Summary: [Model] EModelService - define a more generic 'find' API
Status: VERIFIED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 4.3   Edit
Hardware: PC Windows 7
: P3 normal (vote)
Target Milestone: 4.4 M6   Edit
Assignee: Eric Moffatt CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 429311 (view as bug list)
Depends on: 383403 424345
Blocks: 426553 431714
  Show dependency tree
 
Reported: 2013-12-18 14:00 EST by Eric Moffatt CLA
Modified: 2014-04-07 07:54 EDT (History)
9 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Eric Moffatt CLA 2013-12-18 14:00:53 EST
This idea was generated from the discussion from Laurent Petit regarding his requirements for managing Clojure changes to the model...

Laurent says:

"I need to find all MApplicationElements by tags, and then I inspect them: if they have some specific transient property set, with the right value, then it's ok. If they miss this transient property, I want to remove them."

Now that we've addressed bug 383403 and are looking at bug 424345 perhaps we should take the time to review our approach and see if we can come up with something more generic (a la what we did for the 'createModelElement' API.

I'll add some comments based on some experiments I've done but really need input from EMF experts on what we can / should do...
Comment 1 Eric Moffatt CLA 2013-12-18 14:45:28 EST
We seem to have two different issues:

1) We need to be able to ultimately do a *complete* iteration over the model. Preferably this will be applicable to extended models (one where the UI model has extensions in it) as well as being performant.

While the API itself is TBD we can likely require that the caller provide the type of the elements it wants. Perhaps we can use that info and the model's structure to cull search paths that can't lead to this type.

2) We need to be able to extend this iterator's 'match' filter to include the ability to define a 'custom' filter (which laurent could use to do his whole operation in one step..;-).

Here I have what I think is a reasonable solution (at least for Java). We could create a version of the 'findElements' API that has an 'Object' CF parameter to define a custom filter. This CF object would be expected to contain a method annotated with '@MatchElement' (a new annotation) that returns a boolean (true meaning that it 'passed' the test. I tested this and you *can* make an anonymous inner class directly in the call to the api (paraphrase of laurent's case):

List<MPart> kids = ms.findElements(theApp.getChildren().get(0), null, MPart.class, magicClojureTag, EModelService.PRESENTATION, new Object() {
   @MatchElement
   boolean matchElement(MUIElement elementToTest) {
      return elementToTest.getTransientData().get("Clojure Data) == <value>;
   }
});

The test for the tags is done with the default matcher and if the element passes that test then the custom matcher is invoked (so the custom filter doesn't have to re-check for the tag...).
Comment 2 Thomas Schindl CLA 2013-12-18 14:58:31 EST
What about providing an API which implements the visitor pattern, so we can allow people to more generally walk our model-tree?
Comment 3 Laurent Petit CLA 2013-12-18 16:28:33 EST
I'd like to replace this in the context of the initial need, so that reviewers can ponder whether the proposed solution(s) best solves it (maybe there are better ways to do it).

The need is to manipulate the MApplication at runtime.
It's not specific to Clojure. I'd say it's of interest of every person willing to to live manipulation e.g. in ModelProcessors.

The initial missing point is the ability to get any kind of MApplicationElement, not just MUIElement.

A generic Visitor for the MApplication would probably fit the bill.

More specialized methods, such as the existing findXXX methods on the EModelService, may then only be necessary if they provide additional value in e.g. performance (are they optimized ? Are Elements indexed on Tags, ids, etc. ?)

To be specific, the MApplicationElements that I see missing in the Kepler API are MCommand, MHandler, MKeybinding. But it's just because I've only investigated this part of the API. Probably some more may be missing I'm unaware of, such as Command Categories, Key Binding Tables, etc., etc.
Comment 4 Eric Moffatt CLA 2013-12-19 10:25:52 EST
One thing we'll certainly need is a full Visitor pattern; it's as requirement for the type of thing that Laurent is suggesting. The question I have here is whether we're best off to use the EMF generated one or do our own. I suspect that the EMF generated one will be akin to the generated editor, complete but clunky. It will have the advantage of working with extended models though.

I think it's worth at least investigating whether we can come up with a more optimal one (perhaps even contribute it back to EMF if it's generic enough...;-). 

We have a couple of advantages if we assume the model's containment remains type-specific (including any extensions) and if we require that the caller know the type of what they're looking for. If we know these two things then we can cull the search tree by identifying branches that can't lead to the desired type (i.e. you'll never find an MPart in an MMenu/MToolBar...). It's worth pointing out that this analysis is static and need not be done more than once for a given model.

My agenda here is really to see if we can *reduce* the number of methods in the API rather than following the current pattern of adding new ones on a case-by-case basis (i.e. for MKeyBinding...). Not only will they bulk up the EModelService's API but they also don't work for extended models. By (my) preference the best result would be to define a single new visitor based API along the lines of:

visit(MApplicationElement toSearch, Class toFind, Object visitor)
Comment 5 Eric Moffatt CLA 2013-12-19 10:38:55 EST
Laurent, you'll have noticed that the proposed API still uses an 'Object' as the incoming filter. If most folks think that it's more appropriate to use an interface fine but we should support both approaches IMO (i.e. we'll check the incoming 'Object' to see if it implements the 'IVisitor' interface and if not try the DI way).

I have a *very* string dislike now for framework based 'API'; our 3.x 'framework' has lead us to a place where we have ~50 man *YEARS* of infrastructure implementation (views, editors, dialogs, commands...) that has been so tied up in the 'framework' that it's effectively inextricable (without more man years of work) and thus unusable by e4 RCP, we must not go down this road again.

Now what would be really cool is to see if we can write an 'injector' for Clojure but that's well outside the scope of this defect..;-).
Comment 6 Lars Vogel CLA 2014-01-24 04:59:05 EST
Currently Add-ons can also not be found by the model service. I think we should also add the possibility to find add-ins with this service
Comment 7 Eric Moffatt CLA 2014-02-20 15:21:05 EST
OK, I've revamped the approach based on feedback from John Arthorne. I was trying to figure out how to handle the new API without producing yet another longer 'findElements' method...

The new approach is to create a new public (API) base class 'ElementMatcher' which encapsulates the original parameters used to filter the elements; 'id', 'clazz' and 'tagsToMatch' as well as owning the base implementation of 'matchElement'.

Now we can add a new EModelService method 'findElements(MUIElement root, ElementMatcher matcher, int searchFlags)' and switch the other existing flavors to use the new mechanism (i.e. create an ElementMatcher from the parameters and call the new API.

This means that clients can now subclass ElementMatcher and either sublcass (or even override) the 'matchElement' method to perform whatever checks they need to perform.

This means that we won't be needing any new annotations like @MatchElement...
Comment 8 Thomas Schindl CLA 2014-02-20 15:32:28 EST
How does the ElementMatcher look like is it a SAM Type?
Comment 9 Eric Moffatt CLA 2014-02-20 16:02:38 EST
Well, right now it's a class with a single non-abstract method which provides the current filtering logic. Does that count ?
Comment 10 Eric Moffatt CLA 2014-02-20 16:10:49 EST
(In reply to Lars Vogel from comment #6)
> Currently Add-ons can also not be found by the model service. I think we
> should also add the possibility to find add-ins with this service

Lars, I'd prefer to keep the EModelService for stuff that aren't trivial for a client to implement. For the same reason both Paul and I think we should remove the current 'findHandler' method; since the user has to locate the MBindingContainer themselves surely they are capable of writing a for loop that checks the id...

We're also going to change the 'searchRoot' to be an MApplicationElement. This is mostly to avoid having to do this later (and to match the change we've just made to the 'getContainingContext' API). The initial implementation for iterating through an MApplication will recurse through its list of windows, we can extend this in the future if needed.
Comment 11 Thomas Schindl CLA 2014-02-20 16:15:35 EST
(In reply to Eric Moffatt from comment #9)
> Well, right now it's a class with a single non-abstract method which
> provides the current filtering logic. Does that count ?

No that is not good in terms of Java8 API and lambda expressions - it has to be a functional interface so that one can use lambda expressions in future.

I'd suggest something like:

interface ElementMatcher {
   boolean match(....);

   public static Default implements ElementMatcher {
       boolean match(....) {
         // ....
       }
   }
}
Comment 12 Eric Moffatt CLA 2014-02-20 16:26:21 EST
Interesting, I could also just provide the interface and have my current MatchElement base class extend it.

I'm admittedly a noob as far as lambas go...can you provide an example of where someone might want to use one in this scenario ? 

Just because we have a new hammer (lambdas) doesn't mean that all API should look like a nail...
Comment 13 Timo Kinnunen CLA 2014-02-21 06:55:20 EST
(In reply to comment #12)
> Interesting, I could also just provide the interface and have my current
> MatchElement base class extend it.
> 
> I'm admittedly a noob as far as lambas go...can you provide an example of where
> someone might want to use one in this scenario ?
> 
> Just because we have a new hammer (lambdas) doesn't mean that all API should
> look like a nail...

I don't quite understand the question, but this is what such a lambda could look like in use:

	static void searchAndProcess(String id, Class<? extends MUIElement> clazz, List<String> tagsToMatch) {
		
		Predicate<MUIElement> filter = e -> id.equals(e.getElementId()) && clazz.isInstance(e) && e.getTags().containsAll(tagsToMatch);
		// find elements matching the filter
	}

That's the default implementation except written from the client's point of view. Other ones are also possible:

		Predicate<MUIElement> filter2 = e -> e.isVisible() && e.isToBeRendered();
		Predicate<MUIElement> filter3 = e -> e.getPersistedState().containsKey(CUSTOM_KEY_NAME);
		// etc.
Comment 14 Eric Moffatt CLA 2014-02-21 11:26:28 EST
OK, here's a Gerrit patch:

  https://git.eclipse.org/r/22374

Bases the new API on the existing Selector SAM interface. We provide a default implementation in a new class ElementMatcher which implements Selector and provides the replacement for the current 'match' method.

We've decided to leave the existing API alone due to binary compatibility issues; while opening the scope from MUIElement to MApplicationElement is code compatible it appears that it's not binary compatible.

There's a specific hack in here where the 'findElementsRecursive' method was calling the old API so I check and downcast the searchRoot. This should likely just be changed over to using the new API...

Comments welcome particularly on the existing API's javadocs which have been modified quite a bit.
Comment 15 Thomas Schindl CLA 2014-02-21 11:37:09 EST
Looks good - we could try to push the Selector into a core package and name according to java8 Predicate and make it generic?
Comment 16 Thomas Schindl CLA 2014-02-21 11:38:16 EST
But legt's discuss this in an extra bug - API looks fine to me!
Comment 17 Eric Moffatt CLA 2014-02-21 11:42:25 EST
Some other things to consider:

Should we move the Selector to the end of the parameter list of the new method ?

We should likely also change 'getContainingContext' back to taking an MUIElement based on it breaking binary compatibility. Is there a case where something that is *not* an MUIElement could have anything except the MApplication as its containing context ?

We should remove the 'findHandler' method since it's trivial and it hasn't been released yet.
Comment 18 Thomas Schindl CLA 2014-02-21 11:56:18 EST
Why does it break binary compat, John?
Comment 19 Paul Webster CLA 2014-02-21 12:35:15 EST
(In reply to Thomas Schindl from comment #18)
> Why does it break binary compat, John?

https://wiki.eclipse.org/Evolving_Java-based_APIs_2#Evolving_API_interfaces_-_API_methods

"Change type of a formal parameter 	- 	Breaks compatibility"

My guess is that binary bundles would have the method signature with MUIElement and so won't link correctly (can't find) at runtime with the new method signature.

PW
Comment 20 John Arthorne CLA 2014-02-21 15:31:06 EST
Yes any change to the arguments of a method is not binary compatible. In class files the method signature has concrete type information. Changing a parameter to be a superclass is a strange case - it is usually source compatible but not binary compatible.
Comment 21 Lars Vogel CLA 2014-02-28 08:49:19 EST
*** Bug 429311 has been marked as a duplicate of this bug. ***
Comment 22 Eric Moffatt CLA 2014-02-28 10:43:33 EST
Hmmm, looks like the original patch has already been committed...

I've just pushed

http://git.eclipse.org/c/platform/eclipse.platform.ui.git/commit/?id=59434046ea0ae9c5ee98a802cf9f2f94e13942ae

This takes in the feedback from Paul / Lars; cleans up some of the javadoc as well as moving the 'Selector' argument to the end of the parameter list (should be clearer when making lambda 'inners'...
Comment 23 Eric Moffatt CLA 2014-03-04 14:07:29 EST
Verified in 4.4.0.I20140303-2000.