private @NotNull ResolveResult resolveModule(@NotNull RuntimeModuleId moduleId, @NotNull Map<RuntimeModuleId, RuntimeModuleDescriptorImpl> dependencyPath) {
    ResolveResult cached = myResolveResults.get(moduleId);
    if (cached != null) return cached;

    RawRuntimeModuleRepositoryData rawData = getMainData();
    RawRuntimeModuleDescriptor rawDescriptor = rawData.findDescriptor(moduleId.getStringId());
    if (rawDescriptor == null) {
      if (myAdditionalData != null) {
        for (RawRuntimeModuleRepositoryData data : myAdditionalData) {
          rawDescriptor = data.findDescriptor(moduleId.getStringId());
          if (rawDescriptor != null) {
            rawData = data;
            break;
          }
        }
      }
      if (rawDescriptor == null) {
        List<RuntimeModuleId> failedPath = new ArrayList<>(dependencyPath.size() + 1);
        failedPath.addAll(dependencyPath.keySet());
        failedPath.add(moduleId);
        return new FailedResolveResult(failedPath);
      }
    }
    
    List<String> rawDependencies = rawDescriptor.getDependencies();
    List<RuntimeModuleDescriptor> resolvedDependencies = new ArrayList<>(rawDependencies.size());
    RuntimeModuleDescriptorImpl descriptor = new RuntimeModuleDescriptorImpl(moduleId, rawData.getBasePath(), rawDescriptor.getResourcePaths(), resolvedDependencies);
    dependencyPath.put(moduleId, descriptor);
    for (String dependency : rawDependencies) {
      RuntimeModuleId dependencyId = myInternedModuleIds.computeIfAbsent(dependency, RuntimeModuleId::raw);
      RuntimeModuleDescriptor circularDependency = dependencyPath.get(dependencyId);
      if (circularDependency != null) {
        //the circularDependency instance isn't fully constructed, but it's ok given that RuntimeModuleDescriptorImpl's constructor just stores the passed values 
        resolvedDependencies.add(circularDependency);
        continue;
      }
      
      ResolveResult result = resolveModule(dependencyId, dependencyPath);
      RuntimeModuleDescriptor module = result.getResolvedModule();
      if (module != null) {
        resolvedDependencies.add(module);
      }
      else {
        return result;
      }
    }
    dependencyPath.remove(moduleId);
    SuccessfulResolveResult result = new SuccessfulResolveResult(descriptor);
    myResolveResults.put(moduleId, result);
    return result;
  }
private @NotNull ResolveResult resolveModule(@NotNull RuntimeModuleId moduleId, @NotNull Map<RuntimeModuleId, RuntimeModuleDescriptorImpl> dependencyPath)