PatchExplainer { public List<EntryExplanation> explainPatch( File oldFile, File newFile, RecommendationModifier... recommendationModifiers) throws IOException, InterruptedException { List<EntryExplanation> result = new ArrayList<>(); Map<ByteArrayHolder, MinimalZipEntry> allOldEntries = mapEntries(oldFile); Map<ByteArrayHolder, MinimalZipEntry> allNewEntries = mapEntries(newFile); Map<ByteArrayHolder, MinimalZipEntry> completelyNewEntries = new HashMap<>(allNewEntries); completelyNewEntries.keySet().removeAll(allOldEntries.keySet()); for (Map.Entry<ByteArrayHolder, MinimalZipEntry> entry : completelyNewEntries.entrySet()) { long compressedSize = getCompressedSize(newFile, entry.getValue(), compressor); result.add( new EntryExplanation( new ByteArrayHolder(entry.getValue().getFileNameBytes()), true, null, compressedSize)); } Uncompressor uncompressor = new DeflateUncompressor(); PreDiffExecutor.Builder builder = new PreDiffExecutor.Builder().readingOriginalFiles(oldFile, newFile); for (RecommendationModifier modifier : recommendationModifiers) { builder.withRecommendationModifier(modifier); } PreDiffExecutor executor = builder.build(); PreDiffPlan plan = executor.prepareForDiffing(); try (TempFileHolder oldTemp = new TempFileHolder(); TempFileHolder newTemp = new TempFileHolder(); TempFileHolder deltaTemp = new TempFileHolder()) { for (QualifiedRecommendation qualifiedRecommendation : plan.getQualifiedRecommendations()) { if (qualifiedRecommendation.getReason() == RecommendationReason.COMPRESSED_BYTES_IDENTICAL) { result.add( new EntryExplanation( new ByteArrayHolder(qualifiedRecommendation.getNewEntry().getFileNameBytes()), false, qualifiedRecommendation.getReason(), 0L)); continue; } if (qualifiedRecommendation.getOldEntry().getCrc32OfUncompressedData() == qualifiedRecommendation.getNewEntry().getCrc32OfUncompressedData() && qualifiedRecommendation.getOldEntry().getUncompressedSize() == qualifiedRecommendation.getNewEntry().getUncompressedSize()) { result.add( new EntryExplanation( new ByteArrayHolder(qualifiedRecommendation.getNewEntry().getFileNameBytes()), false, qualifiedRecommendation.getReason(), 0L)); continue; } long oldOffset = qualifiedRecommendation.getOldEntry().getFileOffsetOfCompressedData(); long oldLength = qualifiedRecommendation.getOldEntry().getCompressedSize(); if (qualifiedRecommendation.getRecommendation().uncompressOldEntry) { uncompress(oldFile, oldOffset, oldLength, uncompressor, oldTemp.file); } else { extractCopy(oldFile, oldOffset, oldLength, oldTemp.file); } long newOffset = qualifiedRecommendation.getNewEntry().getFileOffsetOfCompressedData(); long newLength = qualifiedRecommendation.getNewEntry().getCompressedSize(); if (qualifiedRecommendation.getRecommendation().uncompressNewEntry) { uncompress(newFile, newOffset, newLength, uncompressor, newTemp.file); } else { extractCopy(newFile, newOffset, newLength, newTemp.file); } try (FileOutputStream deltaOut = new FileOutputStream(deltaTemp.file); BufferedOutputStream bufferedDeltaOut = new BufferedOutputStream(deltaOut)) { deltaGenerator.generateDelta(oldTemp.file, newTemp.file, bufferedDeltaOut); bufferedDeltaOut.flush(); long compressedDeltaSize = getCompressedSize(deltaTemp.file, 0, deltaTemp.file.length(), compressor); result.add( new EntryExplanation( new ByteArrayHolder(qualifiedRecommendation.getOldEntry().getFileNameBytes()), false, qualifiedRecommendation.getReason(), compressedDeltaSize)); } } } return result; } PatchExplainer(Compressor compressor, DeltaGenerator deltaGenerator); List<EntryExplanation> explainPatch(
      File oldFile, File newFile, RecommendationModifier... recommendationModifiers);  }
@Test public void testExplainPatch_CompressedBytesIdentical() throws Exception { byte[] bytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_6)); save(bytes, oldFile); save(bytes, newFile); PatchExplainer explainer = new PatchExplainer(null, null); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A1_LEVEL_6), false, RecommendationReason.COMPRESSED_BYTES_IDENTICAL, 0); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_CompressedBytesChanged_UncompressedUnchanged() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_6)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_9)); save(oldBytes, oldFile); save(newBytes, newFile); PatchExplainer explainer = new PatchExplainer(null, null); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A1_LEVEL_9), false, RecommendationReason.COMPRESSED_BYTES_CHANGED, 0L); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_CompressedBytesChanged_UncompressedChanged() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_6)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A2_LEVEL_9)); save(oldBytes, oldFile); save(newBytes, newFile); FakeDeltaGenerator fakeDeltaGenerator = new FakeDeltaGenerator( ENTRY_A1_LEVEL_6.getUncompressedBinaryContent(), ENTRY_A2_LEVEL_9.getUncompressedBinaryContent()); FakeCompressor fakeCompressor = new FakeCompressor(FakeDeltaGenerator.OUTPUT.getBytes("US-ASCII")); PatchExplainer explainer = new PatchExplainer(fakeCompressor, fakeDeltaGenerator); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A2_LEVEL_9), false, RecommendationReason.COMPRESSED_BYTES_CHANGED, FakeCompressor.OUTPUT.length()); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_CompressedBytesChanged_UncompressedChanged_Limited() throws Exception { TotalRecompressionLimiter limiter = new TotalRecompressionLimiter(1); byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_6)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A2_LEVEL_9)); save(oldBytes, oldFile); save(newBytes, newFile); FakeDeltaGenerator fakeDeltaGenerator = new FakeDeltaGenerator( ENTRY_A1_LEVEL_6.getCompressedBinaryContent(), ENTRY_A2_LEVEL_9.getCompressedBinaryContent()); FakeCompressor fakeCompressor = new FakeCompressor(FakeDeltaGenerator.OUTPUT.getBytes("US-ASCII")); PatchExplainer explainer = new PatchExplainer(fakeCompressor, fakeDeltaGenerator); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile, limiter); EntryExplanation expected = new EntryExplanation( path(ENTRY_A2_LEVEL_9), false, RecommendationReason.RESOURCE_CONSTRAINED, FakeCompressor.OUTPUT.length()); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_BothEntriesUncompressed_BytesUnchanged() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_STORED)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_STORED)); save(oldBytes, oldFile); save(newBytes, newFile); PatchExplainer explainer = new PatchExplainer(null, null); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A1_STORED), false, RecommendationReason.BOTH_ENTRIES_UNCOMPRESSED, 0L); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_BothEntriesUncompressed_BytesChanged() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_STORED)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A2_STORED)); save(oldBytes, oldFile); save(newBytes, newFile); FakeDeltaGenerator fakeDeltaGenerator = new FakeDeltaGenerator( ENTRY_A1_STORED.getUncompressedBinaryContent(), ENTRY_A2_STORED.getUncompressedBinaryContent()); FakeCompressor fakeCompressor = new FakeCompressor(FakeDeltaGenerator.OUTPUT.getBytes("US-ASCII")); PatchExplainer explainer = new PatchExplainer(fakeCompressor, fakeDeltaGenerator); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A2_STORED), false, RecommendationReason.BOTH_ENTRIES_UNCOMPRESSED, FakeCompressor.OUTPUT.length()); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_CompressedChangedToUncompressed() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_9)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_STORED)); save(oldBytes, oldFile); save(newBytes, newFile); FakeDeltaGenerator fakeDeltaGenerator = new FakeDeltaGenerator( ENTRY_A1_LEVEL_9.getUncompressedBinaryContent(), ENTRY_A1_STORED.getUncompressedBinaryContent()); FakeCompressor fakeCompressor = new FakeCompressor(FakeDeltaGenerator.OUTPUT.getBytes("US-ASCII")); PatchExplainer explainer = new PatchExplainer(fakeCompressor, fakeDeltaGenerator); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A1_STORED), false, RecommendationReason.COMPRESSED_CHANGED_TO_UNCOMPRESSED, FakeCompressor.OUTPUT.length()); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_UncompressedChangedToCompressed() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_STORED)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_6)); save(oldBytes, oldFile); save(newBytes, newFile); FakeDeltaGenerator fakeDeltaGenerator = new FakeDeltaGenerator( ENTRY_A1_STORED.getUncompressedBinaryContent(), ENTRY_A1_LEVEL_6.getUncompressedBinaryContent()); FakeCompressor fakeCompressor = new FakeCompressor(FakeDeltaGenerator.OUTPUT.getBytes("US-ASCII")); PatchExplainer explainer = new PatchExplainer(fakeCompressor, fakeDeltaGenerator); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_A1_LEVEL_6), false, RecommendationReason.UNCOMPRESSED_CHANGED_TO_COMPRESSED, FakeCompressor.OUTPUT.length()); checkExplanation(explanations, expected); }
@Test public void testExplainPatch_NewFile() throws Exception { byte[] oldBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_A1_LEVEL_6)); byte[] newBytes = UnitTestZipArchive.makeTestZip(Collections.singletonList(ENTRY_B_LEVEL_6)); save(oldBytes, oldFile); save(newBytes, newFile); FakeCompressor fakeCompressor = new FakeCompressor(ENTRY_B_LEVEL_6.getCompressedBinaryContent()); PatchExplainer explainer = new PatchExplainer(fakeCompressor, null); List<EntryExplanation> explanations = explainer.explainPatch(oldFile, newFile); EntryExplanation expected = new EntryExplanation( path(ENTRY_B_LEVEL_6), true, null, FakeCompressor.OUTPUT.length()); checkExplanation(explanations, expected); }