Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 320114

Summary: Allow lookup of all subclasses of an EClass on EPackage.Registry
Product: [Modeling] EMF Reporter: Axel Uhl <eclipse>
Component: CoreAssignee: Ed Merks <Ed.Merks>
Status: CLOSED DUPLICATE QA Contact:
Severity: enhancement    
Priority: P3 CC: eclipse, stepper
Version: 2.6.0   
Target Milestone: ---   
Hardware: PC   
OS: Windows XP   
Whiteboard:

Description Axel Uhl CLA 2010-07-16 10:35:19 EDT
Build Identifier: 20100617-1415

a good way to look up all subclasses of a class in the context of a package registry is to visit all packages in an EPackage.Registry and from there fetch all classes, record their superclasses in an inverse map and cache this for subsequent look-ups. A similar requirement has, e.g., been discussed here: http://www.eclipse.org/forums/index.php?t=rview&goto=429695&th=136341 only that there only one package was scanned.

As the cache may consume some space, there should ideally be only one per package registry. Ideally, this could be a method on EPackage.Registry, such as

   Collection<EClass> getAllSubclasses(EClass clazz)

with the cache being maintained inside the RegistryImpl. The code, here in a standalone class, could look like this:

public class AllEClassSubclassesFinder {

    private final EPackage.Registry registry;

    /**
     * Caches the subclasses of all classes held by the Ecore package registry
     */
    private final Map<EClass, Set<EClass>> subclasses;

    /**
     * remembers which packages from the {@link #registry} have already been cached in {@link #oppositeCache}.
     */
    private final Set<EPackage> cachedPackages;
    
    private static AllEClassSubclassesFinder instance;

    public static AllEClassSubclassesFinder getInstance() {
        if (instance == null) {
            instance = new AllEClassSubclassesFinder();
        }
        return instance;
    }

    
    /**
     * Uses the default {@link EPackage.Registry#INSTANCE} package registry.
     */
    public AllEClassSubclassesFinder() {
	this(EPackage.Registry.INSTANCE);
    }

    /**
     * A non-default instance can be created with this constructor, using a specific non-default registry.
     * If you don't need this, please use {@link #getInstance()} to obtain a singleton instance to save the
     * memory required to maintain the cache.
     */
    public AllEClassSubclassesFinder(EPackage.Registry registry) {
	this.registry = registry;
	cachedPackages = new HashSet<EPackage>();
	subclasses = new HashMap<EClass, Set<EClass>>();
    }

    private void updateSubclassCache() {
	Set<String> registryKeys = new HashSet<String>(registry.keySet()); // avoid concurrent modifications
	for (String packageUri : registryKeys) {
	    EPackage ePackage = registry.getEPackage(packageUri);
	    if (!cachedPackages.contains(ePackage)) {
		cachedPackages.add(ePackage);
		cachePackage(ePackage);
	    }
	}
    }

    private void cachePackage(EPackage ePackage) {
	for (EClassifier c : ePackage.getEClassifiers()) {
	    if (c instanceof EClass) {
	        cacheSubclassRelations((EClass) c);
	    }
	}
    }

    private void cacheSubclassRelations(EClass c) {
        for (EClass sup : c.getESuperTypes()) {
            cacheSubclassForSuperclass(c, sup);
        }
    }

    private void cacheSubclassForSuperclass(EClass sub, EClass sup) {
        Set<EClass> subclassesOfSup = subclasses.get(sup);
        if (subclassesOfSup == null) {
            subclassesOfSup = new HashSet<EClass>();
            subclasses.put(sup, subclassesOfSup);
        }
        subclassesOfSup.add(sub);
        for (EClass supsup : sup.getESuperTypes()) {
            cacheSubclassForSuperclass(sub, supsup);
        }
    }

    public Collection<EClass> getAllSubclasses(EClass c) {
        updateSubclassCache();
        Collection<EClass> result = subclasses.get(c);
        if (result == null) {
            result = Collections.emptySet();
        } else {
            result = Collections.unmodifiableCollection(result);
        }
        return result;
    }

}

For compatibility reasons (breaking implementations of EPackage.Registry by adding a method to the interface) it may be useful to introduce a specialized interface, such as EPackage.RegistryWithSubclassesCache which is then implemented by RegistryImpl.

Reproducible: Always
Comment 1 Ed Merks CLA 2010-07-16 10:44:13 EDT
This is brutal stuff.  It forces all packages to be initialized. That's just not acceptable behavior in an Eclipse IDE.  

We do related stuff for child creation extenders, but in this case, packages register their intent to be considered as an extender.  Better would be something where a package declares the packages of the classes that its classes extends so that one could limit the packages being considered.

This is clearly an enhancement request. I'm curious the use case for this.
Comment 2 Axel Uhl CLA 2010-07-16 11:57:34 EDT
To the use cases: we're working on an event manager that can be registered with a ResourceSet and manages event filter tree registrations efficiently. This requires some up-front effort when registering a filter in lieu for good performance when a Notification is being processed. In particular, we support one filter type that registers for events on notifiers of a specific EClass or any subclass thereof. Now you could argue that it is possible to ascend the superclass graph when a Notification reaches the event manager and then "explode" the original Notification into one for all superclasses. However, this would put the performance penalty into the performance-critical phase of Notification processing.

Another use case is the ExtentMap used for the OCL allInstances() expression. It needs to figure out for which subclasses to retrieve their instance elements. The current implementation in LazyExtentMap seems pretty broken as it looks only in a single resource (the one containing the context element of the expression under evaluation) and then iterates them to check for type conformance:

			for (Iterator<Notifier> iter = EcoreUtil.getAllContents(roots); iter.hasNext();) {
				@SuppressWarnings("unchecked")
				E next = (E) iter.next();
				
				if (isInstance(cls, next)) {
					result.add(next);
				}
			}


with roots being initialized by

		if (context.eResource() != null) {
			// the extent is in the resource
			roots = context.eResource().getContents();
		} else {
			// can only search this object tree
			roots = Collections.singleton(context);
		}



In our implementation of an extent map we use visibility declarations between projects containing EMF content and use a query which retrieves all instances of the class and all its subclasses within the visible scope starting from the context object. The query mechanism we use to implement this (the query2 contribution) requires that we expand the list of subclasses at the moment the query is defined.
Comment 3 Ed Merks CLA 2010-07-16 12:24:46 EDT
The first use case seems to call for something that's demand based.  You get a notification, you look at the object, you get its class, then ask the question, does some filter apply.  If that question is expensive to answer, you create some type of cache to store the answer for fast lookup. 

The second use case I don't understand well, but it seems to me you know all the instances and know all the classes of those instances and ought to restrict your sublcass query to those rather than pawing through all things registered in the JVM, including things that might never be part of your OCL context, e.g., you'll pull in every model from WTP.  ALso, given things like dynamic packages that could be loaded into the resource set, you're going to need to take more into account than just a static global set of subclasses.
Comment 4 Axel Uhl CLA 2010-07-18 16:56:04 EDT
I see the point. For the event manager use case we should be able to find a solution along the lines of what you proposed, probably caching superclass hierarchies as classes are seen by the event manager.

For the query2 support I've found out that there's also a way to query for subclasses.

One additional tricky use-case that I forgot to mention we're still wrestling with. For our OCL impact analysis we're doing all kinds of up-front static analysis of OCL expressions. During one of these analyses we're computing whether two classes have overlapping subclass trees. If not, we can cut of large branches of a computation tree.

In our case we now would have to trade the additional cost of loading registered EPackages for the cost of worse up-front analysis or runtime cost during handling individual Notifications which we're really trying to avoid.

Is there any way to get notified when an EPackage is loaded so that we at least can invalidate and repeat our up-front analysis at this point? Is there a way to find out which EPackages have already been loaded so as to at least scan those without triggering the load of additional bundles?

Is there any way to get a notification when in the dynamic case that you mentioned a new subclass to an existing class is introduced? Probably not.

Thanks and best,
-- Axel
Comment 5 Eike Stepper CLA 2010-07-19 03:21:19 EDT
Hi Axel,

I thought that I submitted a request for "package loaded notification" long ago but I could not find it. May have been a private discussion with Ed.

I see one solution to this problem, but it only works if running in OSGi, and I'm sure it's not ideal in all situations:

Listen to the extension registry for new contributions to the org.eclipse.emf.ecore.generated_package extension point (be prepared that the package registry can be modified after your listener is called, depending on the order of listeners that you can not influence). If the new contribution is still represented by a package descriptor, replace this descriptor with one that emits the desired notification.

And save me from Ed's revenge for poisoning one more fountain :P

Cheers
/Eike
Comment 6 Ed Merks CLA 2010-07-19 09:27:50 EDT
I'll mark this as a duplicate of the registry listener request to which Eike refers.

*** This bug has been marked as a duplicate of bug 273989 ***