1 // Copyright 2016 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.archivepatcher.generator; 16 17 import java.io.File; 18 import java.util.ArrayList; 19 import java.util.Collections; 20 import java.util.Comparator; 21 import java.util.List; 22 23 /** 24 * Limits the size of the delta-friendly old blob, which is an implicit limitation on the amount of 25 * temp space required to apply a patch. 26 * 27 * <p>This class implements the following algorithm: 28 * 29 * <ol> 30 * <li>Check the size of the old archive and subtract it from the maximum size, this is the number 31 * of bytes that can be used to uncompress entries in the delta-friendly old file. 32 * <li>Identify all of the {@link QualifiedRecommendation}s that have {@link 33 * Recommendation#uncompressOldEntry} set to <code>true</code>. These identify all the entries 34 * that would be uncompressed in the delta-friendly old file. 35 * <li>Sort those {@link QualifiedRecommendation}s in order of decreasing uncompressed size. 36 * <li>Iterate over the list in order. For each entry, calculate the difference between the 37 * uncompressed size and the compressed size; this is the number of bytes that would be 38 * consumed to transform the data from compressed to uncompressed in the delta-friendly old 39 * file. If the number of bytes that would be consumed is less than the number of bytes 40 * remaining before hitting the cap, retain it; else, discard it. 41 * <li>Return the resulting list of the retained entries. Note that the order of this list may not 42 * be the same as the input order (i.e., it has been sorted in order of decreasing compressed 43 * size). 44 * </ol> 45 */ 46 public class DeltaFriendlyOldBlobSizeLimiter implements RecommendationModifier { 47 48 /** The maximum size of the delta-friendly old blob. */ 49 private final long maxSizeBytes; 50 51 private static final Comparator<QualifiedRecommendation> COMPARATOR = 52 new UncompressedOldEntrySizeComparator(); 53 54 /** 55 * Create a new limiter that will restrict the total size of the delta-friendly old blob. 56 * 57 * @param maxSizeBytes the maximum size of the delta-friendly old blob 58 */ 59 public DeltaFriendlyOldBlobSizeLimiter(long maxSizeBytes) { 60 if (maxSizeBytes < 0) { 61 throw new IllegalArgumentException("maxSizeBytes must be non-negative: " + maxSizeBytes); 62 } 63 this.maxSizeBytes = maxSizeBytes; 64 } 65 66 @Override 67 public List<QualifiedRecommendation> getModifiedRecommendations( 68 File oldFile, File newFile, List<QualifiedRecommendation> originalRecommendations) { 69 70 List<QualifiedRecommendation> sorted = sortRecommendations(originalRecommendations); 71 72 List<QualifiedRecommendation> result = new ArrayList<>(sorted.size()); 73 long bytesRemaining = maxSizeBytes - oldFile.length(); 74 for (QualifiedRecommendation originalRecommendation : sorted) { 75 if (!originalRecommendation.getRecommendation().uncompressOldEntry) { 76 // Keep the original recommendation, no need to track size since it won't be uncompressed. 77 result.add(originalRecommendation); 78 } else { 79 long extraBytesConsumed = 80 originalRecommendation.getOldEntry().getUncompressedSize() 81 - originalRecommendation.getOldEntry().getCompressedSize(); 82 if (bytesRemaining - extraBytesConsumed >= 0) { 83 // Keep the original recommendation, but also subtract from the remaining space. 84 result.add(originalRecommendation); 85 bytesRemaining -= extraBytesConsumed; 86 } else { 87 // Update the recommendation to prevent uncompressing this tuple. 88 result.add( 89 new QualifiedRecommendation( 90 originalRecommendation.getOldEntry(), 91 originalRecommendation.getNewEntry(), 92 Recommendation.UNCOMPRESS_NEITHER, 93 RecommendationReason.RESOURCE_CONSTRAINED)); 94 } 95 } 96 } 97 return result; 98 } 99 100 private static List<QualifiedRecommendation> sortRecommendations( 101 List<QualifiedRecommendation> originalRecommendations) { 102 List<QualifiedRecommendation> sorted = 103 new ArrayList<QualifiedRecommendation>(originalRecommendations); 104 Collections.sort(sorted, COMPARATOR); 105 Collections.reverse(sorted); 106 return sorted; 107 } 108 109 /** Helper class implementing the sort order described in the class documentation. */ 110 private static class UncompressedOldEntrySizeComparator 111 implements Comparator<QualifiedRecommendation> { 112 @Override 113 public int compare(QualifiedRecommendation qr1, QualifiedRecommendation qr2) { 114 return Long.compare( 115 qr1.getOldEntry().getUncompressedSize(), qr2.getOldEntry().getUncompressedSize()); 116 } 117 } 118 } 119