Search code examples
javaxtextemfxtend

Inverse reference lookup


For some reasons my JvmModelInferrer needs to search for all elements of a special type which fulfill a criterion. These elements are necessary to infer the model completely. But all these elements can be spread over all source code files of the project. More precise: There is an element which introduces a class and several elements which modify this class. The grammar for this looks like this (simplified to a minimum depth):

DeltaAction:
    AddsUnit | ModifiesUnit | RemovesUnit;

AddsUnit:
    {AddsUnit} 'adds' '{' unit=JavaCompilationUnit? '}';

JavaCompilationUnit:
    ('package' name=QualifiedName EOL)?
    importSection=XImportSection?
    // ...
    typeDeclarations=ClassOrInterface;

ClassOrInterface:
    ClassDeclaration /* | ... */;

ClassDeclaration:
    'class' name=QualifiedName
    // ...
    ;

ModifiesUnit:
    'modifies' unit=[ClassOrInterface|QualifiedName] '{'
    // ...
    '}';

If I now infer the jvm model for a class pkg.A, I need to find all ModifiesUnit units which reference pkg.A to generate this class.

This is more or less the question: How can I find all elements referencing pkg.A? I found a soultion, but I think it is very inperformant and maybe there is any API which does i for me much more efficient.

class DeltaJJvmModelInferrer extends AbstractModelInferrer {

@Inject ResourceDescriptionsProvider descsProvider
@Inject ResourceSet set
@Inject IQualifiedNameProvider qnameProvider

def dispatch void infer(DeltaJUnit unit, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
    descsProvider.createResourceDescriptions.allResourceDescriptions.forEach [ rd |
            val res = set.getResource(rd.URI, true)
            res.unload
            res.load(null)
            EcoreUtil2.resolveAll(res)
        ]
        try {
            set.allContents.filter(typeof(ModifiesUnit)).filter [ mu |
                qnameProvider.getFullyQualifiedName(mu.unit).equals(qnameProvider.getFullyQualifiedName(cd))
            ].forEach [ mu |
                // Do the stuff I need to do!
            ]
        } catch (Exception e) {
            return
        }
    ]
}

Solution

  • Thanks, Christian Dietrich! Your idea works very good.

    My solution for a fast, specialised reverse reference lookup looks like this:

    1. I extended the XbaseResourceDescriptionStrategy to add custom data to the index. The custom data is a key/value pair which has 'ModifiesUnit' as key and the qualified name of the referenced class (qnp.getFullyQualifiedName(mu.unit)) as value:

      class DeltaJResourceDescriptionStrategy extends XbaseResourceDescriptionStrategy {
      
          public static val TYPE = 'ModifiesUnit'
      
          override def createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) {
              var custom = true
              try {
                  if (eObject instanceof ModifiesUnit) {
                      if (!eObject.eIsProxy) {
                          val qname = qnp.getFullyQualifiedName(eObject.unit)
                          acceptor.accept(EObjectDescription.create(qname, eObject, eObject.createModifiesUnitUserData))
                      }
                  }
              } catch (Exception e) {
                  custom = false
              }
              super.createEObjectDescriptions(eObject, acceptor) && custom
          }
      
          def createModifiesUnitUserData(ModifiesUnit mu) {
              val map = newHashMap
              map.put(TYPE, qualifiedNameProvider.getFullyQualifiedName(mu.unit).toString)
              map
          }
      }
      
    2. I created an index wrapper class which currently only provides a method which returns a list of all ModifiesUnits which modify a given class. It uses the qualified name to identify the modifies units I want to have:

      class DeltaJIndex {
      
          @Inject extension ResourceDescriptionsProvider
          @Inject extension QualifiedNameProvider
          @Inject extension ResourceSet
      
          def getAllResourceDescriptions() {
              createResourceDescriptions.allResourceDescriptions
          }
      
          def getAllModifyUnitsOf(ClassOrInterface ci) {
              val Set<ModifiesUnit> units = newHashSet
              val Set<Resource> resources = newHashSet
              val ciQn = qnProvider.getFullyQualifiedName(ci).toString
      
              rdProvider.getResourceDescriptions(ci.eResource).allResourceDescriptions.forEach [ list |
                  list.exportedObjects.forEach [ object |
                      if (object.userDataKeys.contains(TYPE) && object.getUserData(TYPE) == ciQn) {
                          val res = set.getResource(object.EObjectURI, true)
                          if (!resources.contains(res)) {
                              res.unload
                              res.load(null)
                              resources.add(res)
                          }
                          units.add(res.getEObject(object.EObjectURI.fragment) as ModifiesUnit)
                      }
                  ]
              ]
              units
          }
      }
      

      The only problem is, that I have to unload every resource and load it again. Otherwise the content of any resource is not in the same state if they were edited since the last Eclipse start-up.

    3. Accessing all ModifiesUnits which modify a certain class is now that simple: val modifiesUnits = index.allModifyUnitsForCi(cd).