@Override
 public ModuleSearchResult completeModules(ModuleQuery query) {
   ModuleSearchResult result = new ModuleSearchResult();
   for (CmrRepository root : getRepositories()) {
     if (query.getNamespace() == null || query.getNamespace().equals(root.getNamespace())) {
       root.completeModules(query, result);
     }
   }
   return result;
 }
  @Override
  public ModuleSearchResult searchModules(ModuleQuery query) {
    if (!query.isPaging()) {
      // that's pretty simple
      ModuleSearchResult result = new ModuleSearchResult();
      for (CmrRepository root : getRepositories()) {
        if (query.getNamespace() == null || query.getNamespace().equals(root.getNamespace())) {
          root.searchModules(query, result);
        }
      }
      return result;
    } else {
      // we need to merge manually
      List<CmrRepository> repos = getRepositories();
      ModuleSearchResult[] results = new ModuleSearchResult[repos.size()];
      // keep an overall module name ordering
      SortedSet<String> names = new TreeSet<>();
      int i = 0;
      long[] pagingInfo = query.getPagingInfo();
      if (pagingInfo != null) {
        // check its length
        if (pagingInfo.length != repos.size())
          throw new IllegalArgumentException(
              "Paging info is not the same size as roots, it must have come from a different RepositoryManager");
      }
      Long start = query.getStart();
      for (CmrRepository root : repos) {
        if (query.getNamespace() == null || query.getNamespace().equals(root.getNamespace())) {
          ModuleSearchResult result = new ModuleSearchResult();
          // adapt the start index if required
          if (pagingInfo != null) query.setStart(pagingInfo[i]);
          root.searchModules(query, result);
          results[i++] = result;
          names.addAll(result.getModuleNames());
        }
      }
      // restore the query start
      query.setStart(start);
      // now merge results
      ModuleSearchResult result = new ModuleSearchResult();
      long[] resultPagingInfo = new long[repos.size()];
      // initialise it if we need to
      if (pagingInfo != null)
        System.arraycopy(pagingInfo, 0, resultPagingInfo, 0, resultPagingInfo.length);

      result.setNextPagingInfo(resultPagingInfo);
      i = 0;
      for (String module : names) {
        // stop if we exceeded the count
        if (query.getCount() != null && i++ == query.getCount()) break;
        // collect every module result for that name from the results
        int repo = 0;
        for (ModuleSearchResult resultPart : results) {
          ModuleDetails details = resultPart.getResult(module);
          // did we find anything in that repo?
          if (details == null) {
            repo++;
            continue;
          } else {
            // count one result for this repo
            resultPagingInfo[repo++]++;
          }
          // merge it
          result.addResult(module, details);
        }
      }
      // see if there are any records left in next pages
      int repo = 0;
      for (ModuleSearchResult resultPart : results) {
        // if we had more results in the first place then we must have another page
        if (resultPart.getHasMoreResults()) {
          result.setHasMoreResults(true);
          break;
        }
        // see how many results we added from this repo
        long resultsAddedForThisRepo;
        if (pagingInfo != null) resultsAddedForThisRepo = resultPagingInfo[repo] - pagingInfo[repo];
        else resultsAddedForThisRepo = resultPagingInfo[repo];
        // did we have more results than we put in?
        if (resultPart.getCount() > resultsAddedForThisRepo) {
          result.setHasMoreResults(true);
          break;
        }
        repo++;
      }
      // record where we started (i is one greater than the number of modules added)
      if (query.getStart() != null) result.setStart(query.getStart());
      else result.setStart(0);
      // all done
      return result;
    }
  }