/* @NonNull */
 @Override
 public Iterable<Pair<IStorage, IProject>> getStorages(/* @NonNull */ URI uri) {
   List<Pair<IStorage, IProject>> result = newArrayListWithCapacity(1);
   List<PackageFragmentRootData> packageFragmentRootDatas;
   synchronized (cachedPackageFragmentRootData) {
     packageFragmentRootDatas = newArrayList(cachedPackageFragmentRootData.values());
   }
   Iterator<PackageFragmentRootData> iterator = packageFragmentRootDatas.iterator();
   while (iterator.hasNext()) {
     PackageFragmentRootData data = iterator.next();
     if (data.exists()) {
       if (data.uriPrefix == null || uri.toString().startsWith(data.uriPrefix.toString())) {
         IStorage storage = data.uri2Storage.get(uri);
         if (storage != null) {
           for (IPackageFragmentRoot root : data.associatedRoots.values()) {
             result.add(Tuples.create(storage, root.getJavaProject().getProject()));
           }
         }
       }
     } else {
       iterator.remove();
     }
   }
   if (result.isEmpty() && uri.isArchive()) {
     String authority = uri.authority();
     authority = authority.substring(0, authority.length() - 1);
     URI archiveURI = URI.createURI(authority);
     if (archiveURI.isFile() || archiveURI.isPlatformResource()) {
       IPath archivePath =
           new Path(
               archiveURI.isPlatformResource()
                   ? archiveURI.toPlatformString(true)
                   : archiveURI.toFileString());
       for (PackageFragmentRootData data : packageFragmentRootDatas) {
         if (data.uriPrefix != null && archivePath.equals(data.getPath())) {
           // prefixes have an empty last segment.
           URI prefix =
               data.uriPrefix.lastSegment().length() == 0
                   ? data.uriPrefix.trimSegments(1)
                   : data.uriPrefix;
           URI expectedURI = prefix.appendSegments(uri.segments());
           IStorage storage = data.uri2Storage.get(expectedURI);
           if (storage != null) {
             for (IPackageFragmentRoot root : data.associatedRoots.values()) {
               result.add(Tuples.create(storage, root.getJavaProject().getProject()));
             }
           }
         }
       }
     }
   }
   return result;
 }
 @Override
 public boolean isAffected(
     Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context) {
   Set<URI> outgoingReferences = descriptionUtils.collectOutgoingReferences(candidate);
   if (!outgoingReferences.isEmpty()) {
     for (IResourceDescription.Delta delta : deltas)
       if (hasChanges(delta, candidate) && outgoingReferences.contains(delta.getUri()))
         return true;
   }
   // this is a tradeoff - we could either check whether a given delta uri is contained
   // in a reachable container and check for intersecting names afterwards, or we can do
   // the other way round
   // unfortunately there is no way to decide reliably which algorithm scales better
   // note that this method is called for each description so we have something like a
   // number of deltas x number of resources which is not really nice
   List<IContainer> containers = null;
   Collection<QualifiedName> importedNames = getImportedNames(candidate);
   for (IResourceDescription.Delta delta : deltas) {
     if (hasChanges(delta, candidate)) {
       // not a java resource - delta's resource should be contained in a visible container
       // as long as we did not delete the resource
       URI uri = delta.getUri();
       if ((uri.isPlatform() || uri.isArchive()) && delta.getNew() != null) {
         if (containers == null)
           containers = containerManager.getVisibleContainers(candidate, context);
         boolean descriptionIsContained = false;
         for (int i = 0; i < containers.size() && !descriptionIsContained; i++) {
           descriptionIsContained = containers.get(i).hasResourceDescription(uri);
         }
         if (!descriptionIsContained) return false;
       }
       if (isAffected(importedNames, delta.getNew())
           || isAffected(importedNames, delta.getOld())) {
         return true;
       }
     }
   }
   return false;
 }
 @Override
 public boolean isApplicable(URI uri) {
   return uri.isArchive();
 }