예제 #1
0
  /**
   * Load image data for file and put user data attributes into file.
   *
   * @param file File
   * @return true if file image is loaded.
   * @throws java.io.IOException if image can not be loaded
   */
  private static boolean refresh(@NotNull VirtualFile file) throws IOException {
    Long loadedTimeStamp = file.getUserData(TIMESTAMP_KEY);
    SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
    if (loadedTimeStamp == null
        || loadedTimeStamp.longValue() != file.getTimeStamp()
        || SoftReference.dereference(imageRef) == null) {
      try {
        final byte[] content = file.contentsToByteArray();

        if (ICO_FORMAT.equalsIgnoreCase(file.getExtension())) {
          try {
            final BufferedImage image =
                ICO_IMAGE_PARSER.getBufferedImage(new ByteSourceArray(content), null);
            file.putUserData(FORMAT_KEY, ICO_FORMAT);
            file.putUserData(BUFFERED_IMAGE_REF_KEY, new SoftReference<>(image));
            return true;
          } catch (ImageReadException ignore) {
          }
        }

        InputStream inputStream = new ByteArrayInputStream(content, 0, content.length);
        ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
        try {
          Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
          if (imageReaders.hasNext()) {
            ImageReader imageReader = imageReaders.next();
            try {
              file.putUserData(FORMAT_KEY, imageReader.getFormatName());
              ImageReadParam param = imageReader.getDefaultReadParam();
              imageReader.setInput(imageInputStream, true, true);
              int minIndex = imageReader.getMinIndex();
              BufferedImage image = imageReader.read(minIndex, param);
              file.putUserData(BUFFERED_IMAGE_REF_KEY, new SoftReference<>(image));
              return true;
            } finally {
              imageReader.dispose();
            }
          }
        } finally {
          imageInputStream.close();
        }
      } finally {
        // We perform loading no more needed
        file.putUserData(TIMESTAMP_KEY, file.getTimeStamp());
      }
    }
    return false;
  }
 protected static void touch(VirtualFile file) throws IOException {
   file.setBinaryContent(
       file.contentsToByteArray(), file.getModificationStamp() + 1, file.getTimeStamp() + 1);
   File ioFile = VfsUtil.virtualToIoFile(file);
   assert ioFile.setLastModified(ioFile.lastModified() - 100000);
   file.refresh(false, false);
 }
 public List<ShelvedChangeList> importChangeLists(
     final Collection<VirtualFile> files, final Consumer<VcsException> exceptionConsumer) {
   final List<ShelvedChangeList> result = new ArrayList<ShelvedChangeList>(files.size());
   try {
     final FilesProgress filesProgress = new FilesProgress(files.size(), "Processing ");
     for (VirtualFile file : files) {
       filesProgress.updateIndicator(file);
       final String description = file.getNameWithoutExtension().replace('_', ' ');
       final File patchPath = getPatchPath(description);
       final ShelvedChangeList list =
           new ShelvedChangeList(
               patchPath.getPath(),
               description,
               new SmartList<ShelvedBinaryFile>(),
               file.getTimeStamp());
       try {
         final List<TextFilePatch> patchesList =
             loadPatches(myProject, file.getPath(), new CommitContext());
         if (!patchesList.isEmpty()) {
           FileUtil.copy(new File(file.getPath()), patchPath);
           // add only if ok to read patch
           myShelvedChangeLists.add(list);
           result.add(list);
         }
       } catch (IOException e) {
         exceptionConsumer.consume(new VcsException(e));
       } catch (PatchSyntaxException e) {
         exceptionConsumer.consume(new VcsException(e));
       }
     }
   } finally {
     notifyStateChanged();
   }
   return result;
 }
예제 #4
0
 public long getTimeStamp(@NotNull final VirtualFile file) {
   if (file.getParent() == null) return file.getTimeStamp(); // Optimization
   //        if (file.getParent() == null) return getOriginalFile().lastModified(); // Optimization
   synchronized (lock) {
     final ZipEntry entry = convertToEntry(file);
     return entry != null ? entry.getTime() : -1L;
   }
 }
 public void addDependency(final PsiElement element) {
   final PsiFile psiFile = element.getContainingFile();
   if (psiFile != null) {
     VirtualFile file = psiFile.getVirtualFile();
     if (file != null) {
       addDependency(file.getUrl(), file.getTimeStamp());
     }
   }
 }
 private void copyToTemp(VirtualFile file) {
   try {
     final byte[] bytes = file.contentsToByteArray();
     mySavedCopies.put(file, bytes);
     mySavedTimestamps.put(file, file.getTimeStamp());
   } catch (IOException e) {
     LOG.error(e);
   }
 }
 protected void changeFile(VirtualFile file, final String newText) {
   try {
     if (newText != null) {
       VfsUtil.saveText(file, newText);
     }
     ((NewVirtualFile) file).setTimeStamp(file.getTimeStamp() + 10);
   } catch (IOException e) {
     throw new RuntimeException(e);
   }
 }
  public void testFileDeletion() throws Exception {
    VirtualFile f = myRoot.createChildData(this, "foo.txt");
    f.setBinaryContent(new byte[] {123}, -1, 4000);
    f.delete(this);

    revertLastChange();

    f = myRoot.findChild("foo.txt");
    assertNotNull(f);
    assertEquals(123, f.contentsToByteArray()[0]);
    assertEquals(4000, f.getTimeStamp());
  }
  @Override
  @Nullable
  public ObjectStubTree readFromVFile(Project project, final VirtualFile vFile) {
    if (DumbService.getInstance(project).isDumb()) {
      return null;
    }

    final int id = Math.abs(FileBasedIndex.getFileId(vFile));
    if (id <= 0) {
      return null;
    }

    boolean wasIndexedAlready = FileBasedIndexImpl.isFileIndexed(vFile, StubUpdatingIndex.INDEX_ID);

    final List<SerializedStubTree> datas =
        FileBasedIndex.getInstance()
            .getValues(StubUpdatingIndex.INDEX_ID, id, GlobalSearchScope.fileScope(project, vFile));
    final int size = datas.size();

    if (size == 1) {
      Stub stub;
      try {
        stub = datas.get(0).getStub(false);
      } catch (SerializerNotFoundException e) {
        return processError(
            vFile, "No stub serializer: " + vFile.getPresentableUrl() + ": " + e.getMessage(), e);
      }
      ObjectStubTree tree =
          stub instanceof PsiFileStub
              ? new StubTree((PsiFileStub) stub)
              : new ObjectStubTree((ObjectStubBase) stub, true);
      tree.setDebugInfo(
          "created from index: "
              + StubUpdatingIndex.getIndexingStampInfo(vFile)
              + ", wasIndexedAlready="
              + wasIndexedAlready
              + ", queried at "
              + vFile.getTimeStamp());
      return tree;
    } else if (size != 0) {
      return processError(
          vFile,
          "Twin stubs: "
              + vFile.getPresentableUrl()
              + " has "
              + size
              + " stub versions. Should only have one. id="
              + id,
          null);
    }

    return null;
  }
예제 #10
0
 private static long getTimeStamp(
     @NotNull ExternalProjectSettings externalProjectSettings,
     @NotNull ProjectSystemId externalSystemId) {
   long timeStamp = 0;
   for (ExternalSystemConfigLocator locator :
       ExternalSystemConfigLocator.EP_NAME.getExtensions()) {
     if (!externalSystemId.equals(locator.getTargetExternalSystemId())) {
       continue;
     }
     for (VirtualFile virtualFile : locator.findAll(externalProjectSettings)) {
       timeStamp += virtualFile.getTimeStamp();
     }
   }
   return timeStamp;
 }
  public void testParentRename() throws Exception {
    VirtualFile dir = myRoot.createChildDirectory(this, "dir");
    VirtualFile f = dir.createChildData(this, "foo.txt");
    f.setBinaryContent(new byte[] {123}, -1, 4000);

    dir.rename(this, "dir2");

    revertLastChange();

    assertNull(myRoot.findChild("dir2"));
    dir = myRoot.findChild("dir");
    f = dir.findChild("foo.txt");
    assertNotNull(f);
    assertEquals(123, f.contentsToByteArray()[0]);
    assertEquals(4000, f.getTimeStamp());
  }
  @Override
  @Nullable
  public ObjectStubTree readOrBuild(
      Project project, final VirtualFile vFile, @Nullable PsiFile psiFile) {
    final ObjectStubTree fromIndices = readFromVFile(project, vFile);
    if (fromIndices != null) {
      return fromIndices;
    }

    if (!canHaveStub(vFile)) {
      return null;
    }

    try {
      final FileContent fc = new FileContentImpl(vFile, vFile.contentsToByteArray());
      fc.putUserData(IndexingDataKeys.PROJECT, project);
      if (psiFile != null) {
        fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
        if (!vFile.getFileType().isBinary()) {
          fc.putUserData(
              IndexingDataKeys.FILE_TEXT_CONTENT_KEY, psiFile.getViewProvider().getContents());
        }
        psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
      }
      Stub element;
      try {
        element = StubTreeBuilder.buildStubTree(fc);
      } finally {
        if (psiFile != null) {
          psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
        }
      }
      if (element instanceof PsiFileStub) {
        StubTree tree = new StubTree((PsiFileStub) element);
        tree.setDebugInfo("created from file content, timestamp=" + vFile.getTimeStamp());
        return tree;
      }
    } catch (IOException e) {
      LOG.info(
          e); // content can be not cached yet, and the file can be deleted on disk already, without
      // refresh
    }

    return null;
  }
  public void testRevertingContentChangeFromOldRevisionsWhenDirDoesNotExists() throws Exception {
    VirtualFile dir = myRoot.createChildDirectory(this, "dir");
    VirtualFile f = dir.createChildData(this, "foo.txt");

    f.setBinaryContent(new byte[] {1}, -1, 1000);
    f.setBinaryContent(new byte[] {2}, -1, 2000);

    dir.delete(this);

    revertChange(1);

    dir = myRoot.findChild("dir");
    assertNotNull(dir);
    f = dir.findChild("foo.txt");
    assertNotNull(f);
    assertEquals(1, f.contentsToByteArray()[0]);
    assertEquals(1000, f.getTimeStamp());
  }
  @Nullable
  private SoftReference<TIntIntHashMap> doGetLineMapping(final long date, boolean oldToNew) {
    final VirtualFile f = getVirtualFile();
    final byte[] oldContent;
    synchronized (LOCK) {
      if (myOldContent == null) {
        if (ApplicationManager.getApplication().isDispatchThread()) return null;
        final LocalHistory localHistory = LocalHistory.getInstance();
        byte[] byteContent =
            localHistory.getByteContent(
                f,
                new FileRevisionTimestampComparator() {
                  public boolean isSuitable(long revisionTimestamp) {
                    return revisionTimestamp < date;
                  }
                });

        if (byteContent == null && f.getTimeStamp() > date) {
          byteContent = loadFromVersionControl(date, f);
        }
        myOldContent = new SoftReference<byte[]>(byteContent);
      }
      oldContent = myOldContent.get();
    }

    if (oldContent == null) return null;
    String[] coveredLines = getCoveredLines(oldContent, f);
    final Document document = myDocument;
    if (document == null) return null;
    String[] currentLines = getUpToDateLines(document);

    String[] oldLines = oldToNew ? coveredLines : currentLines;
    String[] newLines = oldToNew ? currentLines : coveredLines;

    Diff.Change change;
    try {
      change = Diff.buildChanges(oldLines, newLines);
    } catch (FilesTooBigForDiffException e) {
      LOG.info(e);
      return null;
    }
    return new SoftReference<TIntIntHashMap>(
        getCoverageVersionToCurrentLineMapping(change, oldLines.length));
  }
  public void testContentChangeWhenDirectoryExists() throws Exception {
    VirtualFile f = myRoot.createChildData(this, "foo.txt");
    f.setBinaryContent(new byte[] {1}, -1, 1000);

    getVcs().beginChangeSet();
    f.rename(this, "bar.txt");
    f.setBinaryContent(new byte[] {2}, -1, 2000);
    getVcs().endChangeSet(null);

    myRoot.createChildDirectory(this, "foo.txt");

    revertChange(1, 0, 1);

    assertNull(myRoot.findChild("bar.txt"));
    f = myRoot.findChild("foo.txt");
    assertNotNull(f);
    assertFalse(f.isDirectory());
    assertEquals(1, f.contentsToByteArray()[0]);
    assertEquals(1000, f.getTimeStamp());
  }
예제 #16
0
  public void testApplyingOnlyLastContent() throws Exception {
    VirtualFile f = root.createChildData(null, "f.txt");
    f.setBinaryContent(new byte[] {1}, -1, 1000);
    f.setBinaryContent(new byte[] {2}, -1, 2000);
    f.setBinaryContent(new byte[] {3}, -1, 3000);
    f.setBinaryContent(new byte[] {4}, -1, 4000);

    LoggingVirtualFileAdapter a = new LoggingVirtualFileAdapter();
    addFileListenerDuring(
        a,
        new RunnableAdapter() {
          @Override
          public void doRun() throws IOException {
            revertChange(root, 2);
          }
        });

    assertEquals("contentChanged ", a.getLog());
    assertEquals(1, f.contentsToByteArray()[0]);
    assertEquals(1000, f.getTimeStamp());
  }
 private static boolean needsRefresh(final VirtualFile file) {
   final VirtualFileSystem fs = file.getFileSystem();
   return fs instanceof NewVirtualFileSystem
       && file.getTimeStamp() != ((NewVirtualFileSystem) fs).getTimeStamp(file);
 }
 MyValidityState(@NotNull Collection<VirtualFile> files) {
   for (VirtualFile file : files) {
     myTimestamps.put(file.getPath(), file.getTimeStamp());
   }
 }
  public void showCoverageInformation(final CoverageSuitesBundle suite) {
    // Store the values of myFile and myEditor in local variables to avoid an NPE after dispose()
    // has been called in the EDT.
    final PsiFile psiFile = myFile;
    final Editor editor = myEditor;
    final Document document = myDocument;
    if (editor == null || psiFile == null || document == null) return;
    final MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true);
    final List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
    final ProjectData data = suite.getCoverageData();
    if (data == null) {
      coverageDataNotFound(suite);
      return;
    }
    final CoverageEngine engine = suite.getCoverageEngine();
    final Set<String> qualifiedNames = engine.getQualifiedNames(psiFile);

    // let's find old content in local history and build mapping from old lines to new one
    // local history doesn't index libraries, so let's distinguish libraries content with other one
    final ProjectFileIndex projectFileIndex =
        ProjectRootManager.getInstance(myProject).getFileIndex();
    final VirtualFile file = psiFile.getVirtualFile();
    LOG.assertTrue(file != null);

    final long fileTimeStamp = file.getTimeStamp();
    final long coverageTimeStamp = suite.getLastCoverageTimeStamp();
    final TIntIntHashMap oldToNewLineMapping;

    // do not show coverage info over cls
    if (engine.isInLibraryClasses(myProject, file)) {
      return;
    }
    // if in libraries content
    if (projectFileIndex.isInLibrarySource(file)) {
      // compare file and coverage timestamps
      if (fileTimeStamp > coverageTimeStamp) {
        showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated"));
        return;
      }
      oldToNewLineMapping = null;
    } else {
      // check local history
      oldToNewLineMapping = getOldToNewLineMapping(coverageTimeStamp);
      if (oldToNewLineMapping == null) {

        // if history for file isn't available let's check timestamps
        if (fileTimeStamp > coverageTimeStamp
            && classesArePresentInCoverageData(data, qualifiedNames)) {
          showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated"));
          return;
        }
      }
    }

    if (editor.getUserData(COVERAGE_HIGHLIGHTERS) != null) {
      // highlighters already collected - no need to do it twice
      return;
    }

    final Module module =
        ApplicationManager.getApplication()
            .runReadAction(
                new Computable<Module>() {
                  @Nullable
                  @Override
                  public Module compute() {
                    return ModuleUtilCore.findModuleForPsiElement(psiFile);
                  }
                });
    if (module != null) {
      if (engine.recompileProjectAndRerunAction(
          module,
          suite,
          () -> CoverageDataManager.getInstance(myProject).chooseSuitesBundle(suite))) {
        return;
      }
    }

    // now if oldToNewLineMapping is null we should use f(x)=id(x) mapping

    // E.g. all *.class files for java source file with several classes
    final Set<File> outputFiles = engine.getCorrespondingOutputFiles(psiFile, module, suite);

    final boolean subCoverageActive =
        CoverageDataManager.getInstance(myProject).isSubCoverageActive();
    final boolean coverageByTestApplicable =
        suite.isCoverageByTestApplicable()
            && !(subCoverageActive && suite.isCoverageByTestEnabled());
    final TreeMap<Integer, LineData> executableLines = new TreeMap<Integer, LineData>();
    final TreeMap<Integer, Object[]> classLines = new TreeMap<Integer, Object[]>();
    final TreeMap<Integer, String> classNames = new TreeMap<Integer, String>();
    class HighlightersCollector {
      private void collect(File outputFile, final String qualifiedName) {
        final ClassData fileData = data.getClassData(qualifiedName);
        if (fileData != null) {
          final Object[] lines = fileData.getLines();
          if (lines != null) {
            final Object[] postProcessedLines =
                suite.getCoverageEngine().postProcessExecutableLines(lines, editor);
            for (Object lineData : postProcessedLines) {
              if (lineData instanceof LineData) {
                final int line = ((LineData) lineData).getLineNumber() - 1;
                final int lineNumberInCurrent;
                if (oldToNewLineMapping != null) {
                  // use mapping based on local history
                  if (!oldToNewLineMapping.contains(line)) {
                    continue;
                  }
                  lineNumberInCurrent = oldToNewLineMapping.get(line);
                } else {
                  // use id mapping
                  lineNumberInCurrent = line;
                }
                LOG.assertTrue(lineNumberInCurrent < document.getLineCount());
                executableLines.put(line, (LineData) lineData);

                classLines.put(line, postProcessedLines);
                classNames.put(line, qualifiedName);

                ApplicationManager.getApplication()
                    .invokeLater(
                        () -> {
                          if (lineNumberInCurrent >= document.getLineCount()) return;
                          final RangeHighlighter highlighter =
                              createRangeHighlighter(
                                  suite.getLastCoverageTimeStamp(),
                                  markupModel,
                                  coverageByTestApplicable,
                                  executableLines,
                                  qualifiedName,
                                  line,
                                  lineNumberInCurrent,
                                  suite,
                                  postProcessedLines);
                          highlighters.add(highlighter);
                        });
              }
            }
          }
        } else if (outputFile != null
            && !subCoverageActive
            && engine.includeUntouchedFileInCoverage(qualifiedName, outputFile, psiFile, suite)) {
          collectNonCoveredFileInfo(
              outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable);
        }
      }
    }

    final HighlightersCollector collector = new HighlightersCollector();
    if (!outputFiles.isEmpty()) {
      for (File outputFile : outputFiles) {
        final String qualifiedName = engine.getQualifiedName(outputFile, psiFile);
        if (qualifiedName != null) {
          collector.collect(outputFile, qualifiedName);
        }
      }
    } else { // check non-compilable classes which present in ProjectData
      for (String qName : qualifiedNames) {
        collector.collect(null, qName);
      }
    }
    ApplicationManager.getApplication()
        .invokeLater(
            () -> {
              if (myEditor != null && highlighters.size() > 0) {
                editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters);
              }
            });

    final DocumentListener documentListener =
        new DocumentAdapter() {
          @Override
          public void documentChanged(final DocumentEvent e) {
            myNewToOldLines = null;
            myOldToNewLines = null;
            List<RangeHighlighter> rangeHighlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS);
            if (rangeHighlighters == null) rangeHighlighters = new ArrayList<RangeHighlighter>();
            int offset = e.getOffset();
            final int lineNumber = document.getLineNumber(offset);
            final int lastLineNumber = document.getLineNumber(offset + e.getNewLength());
            final TextRange changeRange =
                new TextRange(
                    document.getLineStartOffset(lineNumber),
                    document.getLineEndOffset(lastLineNumber));
            for (Iterator<RangeHighlighter> it = rangeHighlighters.iterator(); it.hasNext(); ) {
              final RangeHighlighter highlighter = it.next();
              if (!highlighter.isValid() || TextRange.create(highlighter).intersects(changeRange)) {
                highlighter.dispose();
                it.remove();
              }
            }
            final List<RangeHighlighter> highlighters = rangeHighlighters;
            myUpdateAlarm.cancelAllRequests();
            if (!myUpdateAlarm.isDisposed()) {
              myUpdateAlarm.addRequest(
                  () -> {
                    final TIntIntHashMap newToOldLineMapping =
                        getNewToOldLineMapping(suite.getLastCoverageTimeStamp());
                    if (newToOldLineMapping != null) {
                      ApplicationManager.getApplication()
                          .invokeLater(
                              () -> {
                                if (editor.isDisposed()) return;
                                for (int line = lineNumber; line <= lastLineNumber; line++) {
                                  final int oldLineNumber = newToOldLineMapping.get(line);
                                  final LineData lineData = executableLines.get(oldLineNumber);
                                  if (lineData != null) {
                                    RangeHighlighter rangeHighlighter =
                                        createRangeHighlighter(
                                            suite.getLastCoverageTimeStamp(),
                                            markupModel,
                                            coverageByTestApplicable,
                                            executableLines,
                                            classNames.get(oldLineNumber),
                                            oldLineNumber,
                                            line,
                                            suite,
                                            classLines.get(oldLineNumber));
                                    highlighters.add(rangeHighlighter);
                                  }
                                }
                                editor.putUserData(
                                    COVERAGE_HIGHLIGHTERS,
                                    highlighters.size() > 0 ? highlighters : null);
                              });
                    }
                  },
                  100);
            }
          }
        };
    document.addDocumentListener(documentListener);
    editor.putUserData(COVERAGE_DOCUMENT_LISTENER, documentListener);
  }
 private String getTimeStamp() {
   if (!myFile.isValid()) return "";
   return Long.toString(myFile.getTimeStamp());
 }
  @Override
  @Nullable
  public ObjectStubTree readFromVFile(Project project, final VirtualFile vFile) {
    if (DumbService.getInstance(project).isDumb()) {
      return null;
    }

    final int id = Math.abs(FileBasedIndex.getFileId(vFile));
    if (id <= 0) {
      return null;
    }

    boolean wasIndexedAlready =
        ((FileBasedIndexImpl) FileBasedIndex.getInstance()).isFileUpToDate(vFile);

    Document document = FileDocumentManager.getInstance().getCachedDocument(vFile);
    boolean saved =
        document == null || !FileDocumentManager.getInstance().isDocumentUnsaved(document);

    final List<SerializedStubTree> datas =
        FileBasedIndex.getInstance()
            .getValues(StubUpdatingIndex.INDEX_ID, id, GlobalSearchScope.fileScope(project, vFile));
    final int size = datas.size();

    if (size == 1) {
      SerializedStubTree stubTree = datas.get(0);

      if (!stubTree.contentLengthMatches(
          vFile.getLength(), getCurrentTextContentLength(project, vFile, document))) {
        return processError(
            vFile,
            "Outdated stub in index: "
                + StubUpdatingIndex.getIndexingStampInfo(vFile)
                + ", doc="
                + document
                + ", docSaved="
                + saved
                + ", wasIndexedAlready="
                + wasIndexedAlready
                + ", queried at "
                + vFile.getTimeStamp(),
            null);
      }

      Stub stub;
      try {
        stub = stubTree.getStub(false);
      } catch (SerializerNotFoundException e) {
        return processError(
            vFile, "No stub serializer: " + vFile.getPresentableUrl() + ": " + e.getMessage(), e);
      }
      ObjectStubTree tree =
          stub instanceof PsiFileStub
              ? new StubTree((PsiFileStub) stub)
              : new ObjectStubTree((ObjectStubBase) stub, true);
      tree.setDebugInfo("created from index");
      return tree;
    } else if (size != 0) {
      return processError(
          vFile,
          "Twin stubs: "
              + vFile.getPresentableUrl()
              + " has "
              + size
              + " stub versions. Should only have one. id="
              + id,
          null);
    }

    return null;
  }