Skip to content

Commit

Permalink
[DRAFT] Scoped content adapter
Browse files Browse the repository at this point in the history
ScopedContentAdapter behaves like a normal EMF content adapter, but only
listen to changes in an explicit set of types. It relies on a static
analysis of all the types it can encounter to avoid installing itself
greedily in parts of the model where it can safely determine that no
instance of the types in its scope can be found.

Change-Id: I71d96ed8f2ec4118eca60075b128749990c2b887
  • Loading branch information
pcdavid committed Mar 2, 2016
1 parent e0f3a6c commit 2f6e107
Show file tree
Hide file tree
Showing 12 changed files with 828 additions and 2 deletions.
8 changes: 6 additions & 2 deletions plugins/org.eclipse.sirius.ext.emf/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ Require-Bundle: com.google.guava;bundle-version="[11.0.2,16.0)",
org.eclipse.emf.common;bundle-version="2.8.0",
org.eclipse.emf.ecore;bundle-version="2.8.0",
org.eclipse.emf.ecore.change;bundle-version="2.8.0",
org.eclipse.emf.ecore.xmi;bundle-version="2.8.0"
Export-Package: org.eclipse.sirius.ext.emf;version="2.1.0"
org.eclipse.emf.ecore.xmi;bundle-version="2.8.0",
org.junit;bundle-version="4.11.0",
org.hamcrest.library;bundle-version="1.3.0"
Export-Package: org.eclipse.sirius.ext.emf;version="2.1.0",
org.eclipse.sirius.ext.emf.adapter;version="2.0.0"
Bundle-Vendor: %providerName
Bundle-Activator: org.eclipse.sirius.ext.emf.EMFExtPlugin$Implementation
Import-Package: org.eclipse.sirius.ext.base.relations;version="1.0.0"
Bundle-ActivationPolicy: lazy
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.eclipse.sirius.ext.emf.adapter;

// CHECKSTYLE:OFF
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.InternalEList;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

/**
* A content adapter which is only interested in some types of EObjects, and
* avoids installing itself in parts of the model where it can determine
* statically that no instance of these interesting types can appear.
*
* @author pcdavid
*/
public class ScopedContentAdapter extends EContentAdapter {
private final Set<EClass> scope;

private final TypesAnalyzer analyzer;

public ScopedContentAdapter(TypesAnalyzer analyzer, EClass... scope) {
this(analyzer, Arrays.asList(scope));
}

public ScopedContentAdapter(TypesAnalyzer analyzer, Iterable<EClass> scope) {
this.analyzer = Preconditions.checkNotNull(analyzer);
this.scope = ImmutableSet.copyOf(Preconditions.checkNotNull(scope));
}

@Override
protected void setTarget(EObject target) {
basicSetTarget(target);
for (Iterator<? extends Notifier> i = resolve() ? target.eContents().iterator() : ((InternalEList<? extends Notifier>) target.eContents()).basicIterator(); i.hasNext();) {
Notifier notifier = i.next();
if (notifier instanceof EObject) {
if (inScope((EObject) notifier)) {
addAdapter(notifier);
}
} else {
addAdapter(notifier);
}
}
}

private boolean inScope(EObject target) {
EClass targetType = target.eClass();
if (scope.contains(targetType)) {
return true;
} else {
for (EClass k : scope) {
if (analyzer.canContain(targetType, k)) {
return true;
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package org.eclipse.sirius.ext.emf.adapter;

import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.sirius.ext.base.relations.Relation;
import org.eclipse.sirius.ext.base.relations.TransitiveClosure;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

// CHECKSTYLE:OFF
/**
* Provides useful static analysis on a set of EMF types (EClasses).
*
* @author pcdavid
*/
public final class TypesAnalyzer {
/**
* All the packages considered. This does does includes their sub-packages,
* if any. They must be included explicitly if needed.
*/
private final ImmutableSet<EPackage> packages;

/**
* All the classes in all the packages considered.
*/
private final ImmutableSet<EClass> classes;

/**
* The reflective transitive closure of the relation "x extends y". In other
* words, the map contains the pair (x, y) if and only if x == y or y is a
* direct or indirect super-type of x.
*/
private final ImmutableMultimap<EClass, EClass> superTypesOf;

/**
* The reflective transitive closure of the relation "y extends x". In other
* words, the map contains the pair (x, y) if and only if x == y or x is a
* direct or indirect super-type of y.
*/
private final ImmutableMultimap<EClass, EClass> subTypesOf;

private final Multimap<EClass, EClass> containement;

private TypesAnalyzer(Iterable<EPackage> packages) {
this.packages = ImmutableSet.copyOf(packages);
this.classes = getAllEClasses(this.packages);
this.superTypesOf = computeSuperTypes();
this.subTypesOf = this.superTypesOf.inverse();
this.containement = computeContainments();
}

/**
* Helper method to compute all the direct and indirect sub-packages of a
* set of root packages.
*
* @param roots
* the root packages to consider.
* @return the set of all root packages and all their directly and
* indirectly contained sub-packages.
*/
public static Set<EPackage> withDescendants(Iterable<EPackage> roots) {
Collection<EPackage> result = Lists.newArrayList();
for (EPackage root : roots) {
result.add(root);
result.addAll(withDescendants(root.getESubpackages()));
}
return ImmutableSet.copyOf(result);
}

/**
* Returns an analyzer for all the types in the specified package (not
* including any sub-package).
*
* @param pkg
* the package to analyze.
* @return an analyzer aware of all the types in the specified package.
*/
public static TypesAnalyzer on(EPackage pkg) {
return TypesAnalyzer.on(Collections.singleton(pkg));
}

public static TypesAnalyzer on(Iterable<EPackage> pkgs) {
return new TypesAnalyzer(pkgs);
}

public static TypesAnalyzer on(EPackage.Registry reg) {
return TypesAnalyzer.on(Iterables.filter(reg.values(), EPackage.class));
}

public ImmutableSet<EPackage> getScope() {
return packages;
}

public ImmutableSet<EClass> getAllSubTypes(EClass type) {
return ImmutableSet.copyOf(subTypesOf.get(type));
}

public boolean isSuperTypeOf(EClass superType, EClass subType) {
return subTypesOf.containsEntry(superType, subType);
}

public boolean isSubTypeOf(EClass subType, EClass superType) {
return superTypesOf.containsEntry(subType, superType);
}

public boolean canContain(EClass container, EClass contained) {
return containement.containsEntry(container, contained);
}

private ImmutableSet<EClass> getAllEClasses(Set<EPackage> pkgs) {
Collection<EClass> result = Lists.newArrayList();
for (EPackage ePackage : pkgs) {
Iterables.addAll(result, Iterables.filter(ePackage.getEClassifiers(), EClass.class));
}
return ImmutableSet.copyOf(result);
}

private ImmutableMultimap<EClass, EClass> computeSuperTypes() {
Multimap<EClass, EClass> result = LinkedHashMultimap.create();
for (EClass k : classes) {
result.put(k, k);
result.putAll(k, k.getEAllSuperTypes());
}
return ImmutableMultimap.copyOf(result);
}

private Multimap<EClass, EClass> computeContainments() {
final Multimap<EClass, EClass> directlyContains = HashMultimap.create();
for (EClass container : classes) {
directlyContains.putAll(container, getPossiblyContainedTypes(container));
}
Multimap<EClass, EClass> containment = HashMultimap.create();
Relation<EClass> containmentRel = new TransitiveClosure<EClass>(new Relation<EClass>() {
@Override
public Set<EClass> apply(EClass container) {
return ImmutableSet.copyOf(directlyContains.get(container));
}
});
for (EClass container : classes) {
containment.putAll(container, containmentRel.apply(container));
}
return containment;
}

private Set<EClass> getPossiblyContainedTypes(EClass container) {
Collection<EClass> result = Lists.newArrayList();
for (EClass actualContainerType : subTypesOf.get(container)) {
Set<EReference> containment = getContainmentReferences(actualContainerType);
for (EReference ref : containment) {
result.addAll(subTypesOf.get(ref.getEReferenceType()));
}
}
return ImmutableSet.copyOf(result);
}

private Set<EReference> getContainmentReferences(EClass actualContainerType) {
Collection<EReference> refs = Lists.newArrayList();
for (EReference ref : actualContainerType.getEReferences()) {
if (ref.isContainment()) {
refs.add(ref);
}
}
return ImmutableSet.copyOf(refs);
}
}
7 changes: 7 additions & 0 deletions plugins/org.eclipse.sirius.tests.core/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
28 changes: 28 additions & 0 deletions plugins/org.eclipse.sirius.tests.core/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.sirius.tests.core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
18 changes: 18 additions & 0 deletions plugins/org.eclipse.sirius.tests.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Sirius Tests
Bundle-SymbolicName: org.eclipse.sirius.tests.core
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: org.junit;bundle-version="4.11.0",
com.google.guava;bundle-version="11.0.2",
org.eclipse.sirius.ext.emf;bundle-version="1.0.0",
org.eclipse.sirius;bundle-version="1.0.0",
org.eclipse.sirius.diagram;bundle-version="1.0.0",
org.eclipse.sirius.ui;bundle-version="1.0.0",
org.eclipse.emf.ecore.xmi;bundle-version="2.8.0",
org.hamcrest.library;bundle-version="1.3.0",
org.eclipse.core.runtime;bundle-version="3.8.0",
org.eclipse.sirius.diagram.ui;bundle-version="1.0.0",
org.eclipse.gmf.runtime.notation;bundle-version="1.7.0",
org.eclipse.gmf.runtime.emf.core;bundle-version="1.7.0"
4 changes: 4 additions & 0 deletions plugins/org.eclipse.sirius.tests.core/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.

0 comments on commit 2f6e107

Please sign in to comment.