getDeltaFriendlyNewFileRange() {
87 | return deltaFriendlyNewFileRange;
88 | }
89 |
90 | /**
91 | * Returns the number of bytes of delta data in the patch stream.
92 | * @return as described
93 | */
94 | public long getDeltaLength() {
95 | return deltaLength;
96 | }
97 |
98 | @Override
99 | public int hashCode() {
100 | final int prime = 31;
101 | int result = 1;
102 | result =
103 | prime * result
104 | + ((deltaFriendlyNewFileRange == null) ? 0 : deltaFriendlyNewFileRange.hashCode());
105 | result =
106 | prime * result
107 | + ((deltaFriendlyOldFileRange == null) ? 0 : deltaFriendlyOldFileRange.hashCode());
108 | result = prime * result + (int) (deltaLength ^ (deltaLength >>> 32));
109 | result = prime * result + ((format == null) ? 0 : format.hashCode());
110 | return result;
111 | }
112 |
113 | @Override
114 | public boolean equals(Object obj) {
115 | if (this == obj) return true;
116 | if (obj == null) return false;
117 | if (getClass() != obj.getClass()) return false;
118 | DeltaDescriptor other = (DeltaDescriptor) obj;
119 | if (deltaFriendlyNewFileRange == null) {
120 | if (other.deltaFriendlyNewFileRange != null) return false;
121 | } else if (!deltaFriendlyNewFileRange.equals(other.deltaFriendlyNewFileRange)) return false;
122 | if (deltaFriendlyOldFileRange == null) {
123 | if (other.deltaFriendlyOldFileRange != null) return false;
124 | } else if (!deltaFriendlyOldFileRange.equals(other.deltaFriendlyOldFileRange)) return false;
125 | if (deltaLength != other.deltaLength) return false;
126 | if (format != other.format) return false;
127 | return true;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/applier/src/main/java/com/google/archivepatcher/applier/FileByFileV1DeltaApplier.java:
--------------------------------------------------------------------------------
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.applier;
16 |
17 | import com.google.archivepatcher.applier.bsdiff.BsDiffDeltaApplier;
18 | import com.google.archivepatcher.shared.DeltaFriendlyFile;
19 | import com.google.archivepatcher.shared.RandomAccessFileOutputStream;
20 | import java.io.File;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.io.OutputStream;
24 |
25 | /**
26 | * Applies V1 patches.
27 | */
28 | public class FileByFileV1DeltaApplier implements DeltaApplier {
29 |
30 | /**
31 | * Default size of the buffer to use for copying bytes in the recompression stream.
32 | */
33 | private static final int DEFAULT_COPY_BUFFER_SIZE = 32768;
34 |
35 | /**
36 | * The temp directory to use.
37 | */
38 | private final File tempDir;
39 |
40 | /**
41 | * Creates a new delta applier that will use the default temp directory for working files. This is
42 | * equivalent to calling {@link #FileByFileV1DeltaApplier(File)} with a null
file
43 | * argument.
44 | */
45 | public FileByFileV1DeltaApplier() {
46 | this(null);
47 | }
48 |
49 | /**
50 | * Creates a new delta applier that will use the specified temp directory.
51 | *
52 | * @param tempDir a temp directory where the delta-friendly old blob can be written during the
53 | * patch application process; if null, the system's default temporary directory is used
54 | */
55 | public FileByFileV1DeltaApplier(File tempDir) {
56 | if (tempDir == null) {
57 | tempDir = new File(System.getProperty("java.io.tmpdir"));
58 | }
59 | this.tempDir = tempDir;
60 | }
61 |
62 | @Override
63 | public void applyDelta(File oldBlob, InputStream deltaIn, OutputStream newBlobOut)
64 | throws IOException {
65 | if (!tempDir.exists()) {
66 | // Be nice, try to create the temp directory. Don't bother to check return value as the code
67 | // will fail when it tries to create the file in a few more lines anyways.
68 | tempDir.mkdirs();
69 | }
70 | File tempFile = File.createTempFile("gfbfv1", "old", tempDir);
71 | try {
72 | applyDeltaInternal(oldBlob, tempFile, deltaIn, newBlobOut);
73 | } finally {
74 | tempFile.delete();
75 | }
76 | }
77 |
78 | /**
79 | * Does the work for applying a delta.
80 | * @param oldBlob the old blob
81 | * @param deltaFriendlyOldBlob the location in which to store the delta-friendly old blob
82 | * @param deltaIn the patch stream
83 | * @param newBlobOut the stream to write the new blob to after applying the delta
84 | * @throws IOException if anything goes wrong
85 | */
86 | private void applyDeltaInternal(
87 | File oldBlob, File deltaFriendlyOldBlob, InputStream deltaIn, OutputStream newBlobOut)
88 | throws IOException {
89 |
90 | // First, read the patch plan from the patch stream.
91 | PatchReader patchReader = new PatchReader();
92 | PatchApplyPlan plan = patchReader.readPatchApplyPlan(deltaIn);
93 | writeDeltaFriendlyOldBlob(plan, oldBlob, deltaFriendlyOldBlob);
94 | // Apply the delta. In v1 there is always exactly one delta descriptor, it is bsdiff, and it
95 | // takes up the rest of the patch stream - so there is no need to examine the list of
96 | // DeltaDescriptors in the patch at all.
97 | long deltaLength = plan.getDeltaDescriptors().get(0).getDeltaLength();
98 | DeltaApplier deltaApplier = getDeltaApplier();
99 | // Don't close this stream, as it is just a limiting wrapper.
100 | @SuppressWarnings("resource")
101 | LimitedInputStream limitedDeltaIn = new LimitedInputStream(deltaIn, deltaLength);
102 | // Don't close this stream, as it would close the underlying OutputStream (that we don't own).
103 | @SuppressWarnings("resource")
104 | PartiallyCompressingOutputStream recompressingNewBlobOut =
105 | new PartiallyCompressingOutputStream(
106 | plan.getDeltaFriendlyNewFileRecompressionPlan(),
107 | newBlobOut,
108 | DEFAULT_COPY_BUFFER_SIZE);
109 | deltaApplier.applyDelta(deltaFriendlyOldBlob, limitedDeltaIn, recompressingNewBlobOut);
110 | recompressingNewBlobOut.flush();
111 | }
112 |
113 | /**
114 | * Writes the delta-friendly old blob to temporary storage.
115 | * @param plan the plan to use for uncompressing
116 | * @param oldBlob the blob to turn into a delta-friendly blob
117 | * @param deltaFriendlyOldBlob where to write the blob
118 | * @throws IOException if anything goes wrong
119 | */
120 | private void writeDeltaFriendlyOldBlob(
121 | PatchApplyPlan plan, File oldBlob, File deltaFriendlyOldBlob) throws IOException {
122 | RandomAccessFileOutputStream deltaFriendlyOldFileOut = null;
123 | try {
124 | deltaFriendlyOldFileOut =
125 | new RandomAccessFileOutputStream(
126 | deltaFriendlyOldBlob, plan.getDeltaFriendlyOldFileSize());
127 | DeltaFriendlyFile.generateDeltaFriendlyFile(
128 | plan.getOldFileUncompressionPlan(),
129 | oldBlob,
130 | deltaFriendlyOldFileOut,
131 | false,
132 | DEFAULT_COPY_BUFFER_SIZE);
133 | } finally {
134 | try {
135 | deltaFriendlyOldFileOut.close();
136 | } catch (Exception ignored) {
137 | // Nothing
138 | }
139 | }
140 | }
141 |
142 | /**
143 | * Return an instance of a {@link DeltaApplier} suitable for applying the deltas within the patch
144 | * stream.
145 | * @return the applier
146 | */
147 | // Visible for testing only
148 | protected DeltaApplier getDeltaApplier() {
149 | return new BsDiffDeltaApplier();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/applier/src/main/java/com/google/archivepatcher/applier/LimitedInputStream.java:
--------------------------------------------------------------------------------
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.applier;
16 |
17 | import java.io.FilterInputStream;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 |
21 | /**
22 | * A stream that simulates EOF after a specified numToRead of bytes has been reached.
23 | */
24 | public class LimitedInputStream extends FilterInputStream {
25 | /**
26 | * Current numToRead.
27 | */
28 | private long numToRead;
29 |
30 | /**
31 | * Buffer used for one-byte reads to keep all code on the same path.
32 | */
33 | private byte[] ONE_BYTE = new byte[1];
34 |
35 | /**
36 | * Creates a new limited stream that delegates operations to the specified stream.
37 | * @param in the stream to limit
38 | * @param numToRead the number of reads to allow before returning EOF
39 | */
40 | public LimitedInputStream(InputStream in, long numToRead) {
41 | super(in);
42 | if (numToRead < 0) {
43 | throw new IllegalArgumentException("numToRead must be >= 0: " + numToRead);
44 | }
45 | this.numToRead = numToRead;
46 | }
47 |
48 | @Override
49 | public int read() throws IOException {
50 | if (read(ONE_BYTE, 0, 1) == 1) {
51 | return ONE_BYTE[0];
52 | }
53 | return -1;
54 | }
55 |
56 | @Override
57 | public int read(byte[] b) throws IOException {
58 | return read(b, 0, b.length);
59 | }
60 |
61 | @Override
62 | public int read(byte[] b, int off, int len) throws IOException {
63 | if (numToRead == 0) {
64 | return -1; // Simulate EOF
65 | }
66 | int maxRead = (int) Math.min(len, numToRead);
67 | int numRead = in.read(b, off, maxRead);
68 | if (numRead > 0) {
69 | numToRead -= numRead;
70 | }
71 | return numRead;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/applier/src/main/java/com/google/archivepatcher/applier/PatchApplyPlan.java:
--------------------------------------------------------------------------------
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.applier;
16 |
17 | import com.google.archivepatcher.shared.JreDeflateParameters;
18 | import com.google.archivepatcher.shared.TypedRange;
19 |
20 | import java.util.List;
21 |
22 | /**
23 | * A plan for transforming the old file prior to applying the delta and for recompressing the
24 | * delta-friendly new file afterwards, along with information on the deltas to be applied.
25 | *
26 | * The plan for uncompressing the old file is a {@link List} of {@link TypedRange} entries with void
27 | * metadata. This describes the chunks of the old file that need to be uncompressed prior to
28 | * applying the delta, in file order. The file produced by executing this plan is the
29 | * "delta-friendly" old file.
30 | *
31 | * The plan for recompressing the delta-friendly new file is a {@link List} of {@link TypedRange}
32 | * entries with {@link JreDeflateParameters} metadata. This describes the chunks of the
33 | * delta-friendly new file that need to be recompressed after diffing, again in file order.
34 | * The {@link JreDeflateParameters} metadata indicate the settings to use during recompression. The
35 | * file produced by executing this plan is the new file.
36 | *
37 | * The "plan" for the deltas themselves is a {@link List} of {@link DeltaDescriptor} entries that
38 | * describe the deltas present in the patch stream. Nominally, a {@link PatchReader} is used to
39 | * read the stream up to the first byte of the deltas; the plan for the deltas is ordered in the
40 | * same order as the patch stream and contains the byte length of each delta, so it is then trivial
41 | * to read each delta in order and apply it.
42 | */
43 | public class PatchApplyPlan {
44 | /**
45 | * The plan for uncompressing the old file, in file order.
46 | */
47 | private final List> oldFileUncompressionPlan;
48 |
49 | /**
50 | * The plan for recompressing the delta-friendly new file, in file order.
51 | */
52 | private final List> deltaFriendlyNewFileRecompressionPlan;
53 |
54 | /**
55 | * The expected size of the delta-friendly old file after executing the
56 | * {@link #oldFileUncompressionPlan}.
57 | */
58 | private final long deltaFriendlyOldFileSize;
59 |
60 | /**
61 | * The delta descriptors that describe how and what to do to the delta-friendly old file.
62 | */
63 | private final List deltaDescriptors;
64 |
65 | /**
66 | * Constructs a new plan.
67 | * @param oldFileUncompressionPlan the plan for uncompressing the old file, in file order
68 | * @param deltaFriendlyOldFileSize the expected size of the delta-friendly old file, after
69 | * executing the plan in oldFileUncompressionPlan; this can be used to pre-allocate the necessary
70 | * space to hold the delta-friendly old file
71 | * @param deltaFriendlyNewFileRecompressionPlan the plan for recompressing the delta-friendly new
72 | * file, in file order
73 | * @param deltaDescriptors the descriptors for the deltas in the patch stream
74 | */
75 | public PatchApplyPlan(
76 | List> oldFileUncompressionPlan,
77 | long deltaFriendlyOldFileSize,
78 | List> deltaFriendlyNewFileRecompressionPlan,
79 | List deltaDescriptors) {
80 | this.oldFileUncompressionPlan = oldFileUncompressionPlan;
81 | this.deltaFriendlyOldFileSize = deltaFriendlyOldFileSize;
82 | this.deltaFriendlyNewFileRecompressionPlan = deltaFriendlyNewFileRecompressionPlan;
83 | this.deltaDescriptors = deltaDescriptors;
84 | }
85 |
86 | /**
87 | * Returns the old file uncompression plan.
88 | * @return as described
89 | */
90 | public List> getOldFileUncompressionPlan() {
91 | return oldFileUncompressionPlan;
92 | }
93 |
94 | /**
95 | * Returns the delta-friendly new file recompression plan.
96 | * @return as described
97 | */
98 | public List> getDeltaFriendlyNewFileRecompressionPlan() {
99 | return deltaFriendlyNewFileRecompressionPlan;
100 | }
101 |
102 | /**
103 | * Returns the expected size of the delta-friendly old file after executing the plan returned by
104 | * {@link #getOldFileUncompressionPlan()}. This can be used to pre-allocate the necessary space to
105 | * hold the delta-friendly old file.
106 | * @return as described
107 | */
108 | public long getDeltaFriendlyOldFileSize() {
109 | return deltaFriendlyOldFileSize;
110 | }
111 |
112 | /**
113 | * Returns the delta descriptors that describe how and what to do to the delta-friendly old file.
114 | * @return as described
115 | */
116 | public List getDeltaDescriptors() {
117 | return deltaDescriptors;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/applier/src/main/java/com/google/archivepatcher/applier/PatchFormatException.java:
--------------------------------------------------------------------------------
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.applier;
16 |
17 | import java.io.IOException;
18 |
19 | /**
20 | * Thrown when there is an error in the format of a patch.
21 | */
22 | @SuppressWarnings("serial")
23 | public class PatchFormatException extends IOException {
24 |
25 | /**
26 | * Constructs a new exception with the specified message.
27 | * @param message the message
28 | */
29 | public PatchFormatException(String message) {
30 | super(message);
31 | }
32 |
33 | /**
34 | * Constructs a new exception with the specified message and cause.
35 | * @param message the message
36 | * @param cause the cause of the error
37 | */
38 | public PatchFormatException(String message, Throwable cause) {
39 | super(message);
40 | initCause(cause);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/applier/src/main/java/com/google/archivepatcher/applier/bsdiff/BsDiffDeltaApplier.java:
--------------------------------------------------------------------------------
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.applier.bsdiff;
16 |
17 | import com.google.archivepatcher.applier.DeltaApplier;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 | import java.io.OutputStream;
23 | import java.io.RandomAccessFile;
24 |
25 | /**
26 | * An implementation of {@link DeltaApplier} that uses {@link BsPatch} to apply a bsdiff patch.
27 | */
28 | public class BsDiffDeltaApplier implements DeltaApplier {
29 |
30 | @Override
31 | public void applyDelta(File oldBlob, InputStream deltaIn, OutputStream newBlobOut)
32 | throws IOException {
33 | RandomAccessFile oldBlobRaf = null;
34 | try {
35 | oldBlobRaf = new RandomAccessFile(oldBlob, "r");
36 | BsPatch.applyPatch(oldBlobRaf, newBlobOut, deltaIn);
37 | } finally {
38 | try {
39 | oldBlobRaf.close();
40 | } catch (Exception ignored) {
41 | // Nothing
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/applier/src/test/java/com/google/archivepatcher/applier/LimitedInputStreamTest.java:
--------------------------------------------------------------------------------
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.applier;
16 |
17 | import org.junit.Assert;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.junit.runners.JUnit4;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 |
25 | /**
26 | * Tests for {@link PartiallyCompressingOutputStream}.
27 | */
28 | @RunWith(JUnit4.class)
29 | @SuppressWarnings("javadoc")
30 | public class LimitedInputStreamTest {
31 | /**
32 | * An input stream that never ends and always does nothing.
33 | */
34 | private static class ForeverInputStream extends InputStream {
35 | @Override
36 | public int read() throws IOException {
37 | return 0;
38 | }
39 |
40 | @Override
41 | public int read(byte[] b) throws IOException {
42 | return b.length;
43 | }
44 |
45 | @Override
46 | public int read(byte[] b, int off, int len) throws IOException {
47 | return len;
48 | }
49 | }
50 |
51 | @SuppressWarnings("resource")
52 | @Test
53 | public void testRead_WithLimit0() throws IOException {
54 | LimitedInputStream stream = new LimitedInputStream(new ForeverInputStream(), 0);
55 | Assert.assertEquals(-1, stream.read());
56 | }
57 |
58 | @SuppressWarnings("resource")
59 | @Test
60 | public void testRead_WithLimit1() throws IOException {
61 | LimitedInputStream stream = new LimitedInputStream(new ForeverInputStream(), 1);
62 | Assert.assertEquals(0, stream.read());
63 | Assert.assertEquals(-1, stream.read());
64 | }
65 |
66 | @SuppressWarnings("resource")
67 | @Test
68 | public void testRead_WithLimit100() throws IOException {
69 | LimitedInputStream stream = new LimitedInputStream(new ForeverInputStream(), 100);
70 | Assert.assertEquals(100, stream.read(new byte[1000]));
71 | Assert.assertEquals(-1, stream.read());
72 | }
73 |
74 | @SuppressWarnings("resource")
75 | @Test(expected = IllegalArgumentException.class)
76 | public void testSetLimit_BadValue() {
77 | new LimitedInputStream(new ForeverInputStream(), -1);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/applier/src/test/resources/com/google/archivepatcher/applier/bsdiff/testdata/bsdifftest_internal_blob_a.bin:
--------------------------------------------------------------------------------
1 | TL?I"a)tAARPRLH)ZNuYRBZ>+A2lO;D#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk}#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bKo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk} evaluationDependsOn( subproject.path ) }
21 | jar.dependsOn subprojects.tasks['classes']
22 | jar {
23 | manifest {
24 | attributes 'Implementation-Title': 'Archive Patcher',
25 | 'Implementation-Version': version,
26 | 'Implementation-Vendor': 'https://github.com/andrewhayden/archive-patcher'
27 | }
28 | baseName = project.name
29 | subprojects.each { subproject ->
30 | from subproject.sourceSets.main.output
31 | }
32 | from "LICENSE"
33 | }
34 |
35 | task sourcesJar(type: Jar, dependsOn: classes) {
36 | manifest {
37 | attributes 'Implementation-Title': 'Archive Patcher',
38 | 'Implementation-Version': version,
39 | 'Implementation-Vendor': 'https://github.com/andrewhayden/archive-patcher'
40 | }
41 | baseName = project.name
42 | classifier = 'sources'
43 | subprojects.each { subproject ->
44 | from subproject.sourceSets.main.allSource
45 | }
46 | from "LICENSE"
47 | }
48 |
49 | task javadocJar (type: Jar, dependsOn: javadoc) {
50 | manifest {
51 | attributes 'Implementation-Title': 'Archive Patcher',
52 | 'Implementation-Version': version,
53 | 'Implementation-Vendor': 'https://github.com/andrewhayden/archive-patcher'
54 | }
55 | baseName = project.name
56 | classifier = 'javadoc'
57 | subprojects.each { subproject ->
58 | from subproject.javadoc.destinationDir
59 | }
60 | from "LICENSE"
61 | }
62 |
63 | dependencies {
64 | compile project(':tools')
65 | }
66 |
67 | allprojects {
68 | repositories {
69 | jcenter()
70 | }
71 | }
72 |
73 | artifacts {
74 | archives jar
75 | archives sourcesJar
76 | archives javadocJar
77 | }
78 |
79 | publishing {
80 | publications {
81 | MyPublication(MavenPublication) {
82 | from components.java
83 | groupId 'com.google'
84 | artifactId 'archivepatcher'
85 | version version
86 | }
87 | }
88 | }
89 |
90 | bintray {
91 | user = System.getenv('BINTRAY_USER')
92 | key = System.getenv('BINTRAY_KEY')
93 | publications = ['MyPublication']
94 | filesSpec {
95 | from 'build/libs'
96 | into '/'
97 | }
98 | pkg {
99 | repo = 'Maven'
100 | name = 'archive-patcher'
101 | desc = 'Archive Patcher'
102 | licenses = ['Apache-2.0']
103 | websiteUrl = 'https://github.com/andrewhayden/archive-patcher'
104 | issueTrackerUrl = 'https://github.com/andrewhayden/archive-patcher/issues'
105 | vcsUrl = 'https://github.com/andrewhayden/archive-patcher.git'
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/explainer/build.gradle:
--------------------------------------------------------------------------------
1 | // explainer module
2 | apply plugin: 'java'
3 |
4 | dependencies {
5 | compile project(':generator')
6 | compile project(':shared')
7 |
8 | testCompile 'junit:junit:4.12'
9 | testCompile project(':sharedtest')
10 | }
11 | // EOF
12 |
--------------------------------------------------------------------------------
/explainer/src/main/java/com/google/archivepatcher/explainer/EntryExplanation.java:
--------------------------------------------------------------------------------
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.explainer;
16 |
17 | import com.google.archivepatcher.generator.ByteArrayHolder;
18 | import com.google.archivepatcher.generator.RecommendationReason;
19 |
20 | /**
21 | * The explanation for a single entry that was considered during generation of patch.
22 | */
23 | public class EntryExplanation {
24 | /**
25 | * The path of the entry in the new archive.
26 | */
27 | private final ByteArrayHolder path;
28 |
29 | /**
30 | * True if the entry only exists in the new archive.
31 | */
32 | private final boolean isNew;
33 |
34 | /**
35 | * If the entry is not new, the reason for its inclusion in or exclusion from the patch.
36 | */
37 | private final RecommendationReason reasonIncludedIfNotNew;
38 |
39 | /**
40 | * The approximate size of the entry in the patch stream.
41 | */
42 | private final long compressedSizeInPatch;
43 |
44 | /**
45 | * Construct a new explanation for an entry.
46 | * @param path the path of the entry in the new archive
47 | * @param isNew true if the entry only exists in the new archive
48 | * @param reasonIncludedIfNotNew when isNew is false, the reason that the entry is included
49 | * @param compressedSizeInPatch the approximate size of the entry in the patch
50 | * stream
51 | */
52 | public EntryExplanation(
53 | ByteArrayHolder path,
54 | boolean isNew,
55 | RecommendationReason reasonIncludedIfNotNew,
56 | long compressedSizeInPatch) {
57 | super();
58 | this.path = path;
59 | this.isNew = isNew;
60 | this.reasonIncludedIfNotNew = reasonIncludedIfNotNew;
61 | this.compressedSizeInPatch = compressedSizeInPatch;
62 | }
63 |
64 | /**
65 | * Returns the path of the entry in the new archive.
66 | * @return as described
67 | */
68 | public ByteArrayHolder getPath() {
69 | return path;
70 | }
71 |
72 | /**
73 | * Returns true if the entry only exists in the new archive.
74 | * @return as described
75 | */
76 | public boolean isNew() {
77 | return isNew;
78 | }
79 |
80 | /**
81 | * When {@link #isNew()} is false, the reason that the entry is included.
82 | * @return as described
83 | */
84 | public RecommendationReason getReasonIncludedIfNotNew() {
85 | return reasonIncludedIfNotNew;
86 | }
87 |
88 | /**
89 | * Returns the approximate size of the entry in the patch stream. This number is
90 | * not guaranteed to be precise. Patch generation is complex, and in some cases
91 | * the patching process may use arbitrary bytes from arbitrary locations in the old archive to
92 | * populate bytes in the new archive. Other factors may also contribute to inaccuracies, such as
93 | * overhead in the patch format itself or in compression technology, etceteras.
94 | * @return as described
95 | */
96 | public long getCompressedSizeInPatch() {
97 | return compressedSizeInPatch;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/generator/build.gradle:
--------------------------------------------------------------------------------
1 | // generator module
2 |
3 | apply plugin: 'java'
4 |
5 | dependencies {
6 | compile project(':shared')
7 |
8 | testCompile 'junit:junit:4.12'
9 | testCompile project(':sharedtest')
10 | }
11 |
12 | task copyTestResources(type: Copy) {
13 | // AS/IntelliJ workaround: https://code.google.com/p/android/issues/detail?id=64887#c26
14 | if (System.properties['idea.platform.prefix'] != null) {
15 | from sourceSets.test.resources
16 | into sourceSets.test.output.classesDir
17 | }
18 | }
19 |
20 | processTestResources.dependsOn copyTestResources
21 |
22 | // EOF
23 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/ByteArrayHolder.java:
--------------------------------------------------------------------------------
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.util.Arrays;
18 |
19 | /**
20 | * Holds an array of bytes, implementing {@link #equals(Object)}, {@link #hashCode()} with deep
21 | * comparisons. This is intended primarily to allow raw, uninterpreted paths from
22 | * {@link MinimalZipEntry#getFileNameBytes()} to be used as map keys safely.
23 | */
24 | public class ByteArrayHolder {
25 | /**
26 | * The backing byte array.
27 | */
28 | private final byte[] data;
29 |
30 | /**
31 | * Construct a new wrapper around the specified bytes.
32 | * @param data the byte array
33 | */
34 | public ByteArrayHolder(byte[] data) {
35 | this.data = data;
36 | }
37 |
38 | /**
39 | * Returns the actual byte array that backs the text.
40 | * @return the array
41 | */
42 | public byte[] getData() {
43 | return data;
44 | }
45 |
46 | @Override
47 | public int hashCode() {
48 | final int prime = 31;
49 | int result = 1;
50 | result = prime * result + Arrays.hashCode(data);
51 | return result;
52 | }
53 |
54 | @Override
55 | public boolean equals(Object obj) {
56 | if (this == obj) return true;
57 | if (obj == null) return false;
58 | if (getClass() != obj.getClass()) return false;
59 | ByteArrayHolder other = (ByteArrayHolder) obj;
60 | if (!Arrays.equals(data, other.data)) return false;
61 | return true;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/DeltaFriendlyOldBlobSizeLimiter.java:
--------------------------------------------------------------------------------
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 | * This class implements the following algorithm:
28 | *
29 | *
30 | * - 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 | *
- Identify all of the {@link QualifiedRecommendation}s that have {@link
33 | * Recommendation#uncompressOldEntry} set to
true
. These identify all the entries
34 | * that would be uncompressed in the delta-friendly old file.
35 | * - Sort those {@link QualifiedRecommendation}s in order of decreasing uncompressed size.
36 | *
- 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 | *
- 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 | *
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 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 getModifiedRecommendations(
68 | File oldFile, File newFile, List originalRecommendations) {
69 |
70 | List sorted = sortRecommendations(originalRecommendations);
71 |
72 | List 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 sortRecommendations(
101 | List originalRecommendations) {
102 | List sorted =
103 | new ArrayList(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 {
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 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/DeltaGenerator.java:
--------------------------------------------------------------------------------
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.io.IOException;
19 | import java.io.OutputStream;
20 |
21 | /**
22 | * An interface to be implemented by delta generators.
23 | */
24 | public interface DeltaGenerator {
25 | /**
26 | * Generates a delta in deltaOut that can be applied to oldBlob to produce newBlob.
27 | *
28 | * @param oldBlob the old blob
29 | * @param newBlob the new blob
30 | * @param deltaOut the stream to write the delta to
31 | * @throws IOException in the event of an I/O error reading the input files or writing to the
32 | * delta output stream
33 | * @throws InterruptedException if any thread has interrupted the current thread
34 | */
35 | public void generateDelta(File oldBlob, File newBlob, OutputStream deltaOut)
36 | throws IOException, InterruptedException;
37 | }
38 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/FileByFileV1DeltaGenerator.java:
--------------------------------------------------------------------------------
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 com.google.archivepatcher.generator.bsdiff.BsDiffDeltaGenerator;
18 | import java.io.BufferedOutputStream;
19 | import java.io.File;
20 | import java.io.FileOutputStream;
21 | import java.io.IOException;
22 | import java.io.OutputStream;
23 | import java.util.Arrays;
24 | import java.util.Collections;
25 | import java.util.List;
26 |
27 | /**
28 | * Generates file-by-file patches.
29 | */
30 | public class FileByFileV1DeltaGenerator implements DeltaGenerator {
31 |
32 | /** Optional modifiers for planning and patch generation. */
33 | private final List recommendationModifiers;
34 |
35 | /**
36 | * Constructs a new generator for File-by-File v1 patches, using the specified configuration.
37 | *
38 | * @param recommendationModifiers optionally, {@link RecommendationModifier}s to use for modifying
39 | * the planning phase of patch generation. These can be used to, e.g., limit the total amount
40 | * of recompression that a patch applier needs to do. Modifiers are applied in the order they
41 | * are specified.
42 | */
43 | public FileByFileV1DeltaGenerator(RecommendationModifier... recommendationModifiers) {
44 | if (recommendationModifiers != null) {
45 | this.recommendationModifiers =
46 | Collections.unmodifiableList(Arrays.asList(recommendationModifiers));
47 | } else {
48 | this.recommendationModifiers = Collections.emptyList();
49 | }
50 | }
51 |
52 | /**
53 | * Generate a V1 patch for the specified input files and write the patch to the specified {@link
54 | * OutputStream}. The written patch is raw, i.e. it has not been compressed. Compression
55 | * should almost always be applied to the patch, either right in the specified {@link
56 | * OutputStream} or in a post-processing step, prior to transmitting the patch to the patch
57 | * applier.
58 | *
59 | * @param oldFile the original old file to read (will not be modified)
60 | * @param newFile the original new file to read (will not be modified)
61 | * @param patchOut the stream to write the patch to
62 | * @throws IOException if unable to complete the operation due to an I/O error
63 | * @throws InterruptedException if any thread has interrupted the current thread
64 | */
65 | @Override
66 | public void generateDelta(File oldFile, File newFile, OutputStream patchOut)
67 | throws IOException, InterruptedException {
68 | try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder();
69 | TempFileHolder deltaFriendlyNewFile = new TempFileHolder();
70 | TempFileHolder deltaFile = new TempFileHolder();
71 | FileOutputStream deltaFileOut = new FileOutputStream(deltaFile.file);
72 | BufferedOutputStream bufferedDeltaOut = new BufferedOutputStream(deltaFileOut)) {
73 | PreDiffExecutor.Builder builder =
74 | new PreDiffExecutor.Builder()
75 | .readingOriginalFiles(oldFile, newFile)
76 | .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file);
77 | for (RecommendationModifier modifier : recommendationModifiers) {
78 | builder.withRecommendationModifier(modifier);
79 | }
80 | PreDiffExecutor executor = builder.build();
81 | PreDiffPlan preDiffPlan = executor.prepareForDiffing();
82 | DeltaGenerator deltaGenerator = getDeltaGenerator();
83 | deltaGenerator.generateDelta(
84 | deltaFriendlyOldFile.file, deltaFriendlyNewFile.file, bufferedDeltaOut);
85 | bufferedDeltaOut.close();
86 | PatchWriter patchWriter =
87 | new PatchWriter(
88 | preDiffPlan,
89 | deltaFriendlyOldFile.file.length(),
90 | deltaFriendlyNewFile.file.length(),
91 | deltaFile.file);
92 | patchWriter.writeV1Patch(patchOut);
93 | }
94 | }
95 |
96 | /**
97 | * Generate a V1 patch pre diffing plan.
98 | *
99 | * @param oldFile the original old file to read (will not be modified)
100 | * @param newFile the original new file to read (will not be modified)
101 | * @return the plan
102 | * @throws IOException if unable to complete the operation due to an I/O error
103 | * @throws InterruptedException if any thread has interrupted the current thread
104 | */
105 | public PreDiffPlan generatePreDiffPlan(File oldFile, File newFile)
106 | throws IOException, InterruptedException {
107 | try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder();
108 | TempFileHolder deltaFriendlyNewFile = new TempFileHolder()) {
109 | PreDiffExecutor.Builder builder =
110 | new PreDiffExecutor.Builder()
111 | .readingOriginalFiles(oldFile, newFile)
112 | .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file);
113 | for (RecommendationModifier modifier : recommendationModifiers) {
114 | builder.withRecommendationModifier(modifier);
115 | }
116 |
117 | PreDiffExecutor executor = builder.build();
118 |
119 | return executor.prepareForDiffing();
120 | }
121 | }
122 |
123 | // Visible for testing only
124 | protected DeltaGenerator getDeltaGenerator() {
125 | return new BsDiffDeltaGenerator();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/MatchingOutputStream.java:
--------------------------------------------------------------------------------
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.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | /**
22 | * A simple {@link OutputStream} that requires all bytes that are written to match bytes from a
23 | * specified corresponding {@link InputStream}. Any length or content mismatch results in the stream
24 | * being closed and an error being raised.
25 | *
26 | * Every call to one of the write(...) methods results in reading the same total number of bytes
27 | * from the {@link InputStream}. The bytes in both streams must match exactly.
28 | */
29 | public class MatchingOutputStream extends OutputStream {
30 |
31 | /**
32 | * The bytes to match against.
33 | */
34 | private final InputStream expectedBytesStream;
35 |
36 | /**
37 | * The buffer for reading bytes from the input stream for matching.
38 | */
39 | private final byte[] buffer;
40 |
41 | /**
42 | * Constructs a new stream that will match against the the specified {@link InputStream}.
43 | * @param expectedBytesStream stream of bytes to expect to see
44 | * @param matchBufferSize the number of bytes to reserve for matching against the specified
45 | * {@link InputStream}. This
46 | */
47 | public MatchingOutputStream(InputStream expectedBytesStream, int matchBufferSize) {
48 | if (matchBufferSize < 1) {
49 | throw new IllegalArgumentException("buffer size must be >= 1");
50 | }
51 | this.expectedBytesStream = expectedBytesStream;
52 | this.buffer = new byte[matchBufferSize];
53 | }
54 |
55 | @Override
56 | public void write(int b) throws IOException {
57 | int expected = expectedBytesStream.read();
58 | if (expected == -1) {
59 | throw new MismatchException("EOF reached in expectedBytesStream");
60 | }
61 | if (expected != b) {
62 | throw new MismatchException("Data does not match");
63 | }
64 | }
65 |
66 | @Override
67 | public void write(byte[] b) throws IOException {
68 | write(b, 0, b.length);
69 | }
70 |
71 | @Override
72 | public void write(byte[] dataToWrite, int offset, int length) throws IOException {
73 | int numReadSoFar = 0;
74 | while (numReadSoFar < length) {
75 | int maxToRead = Math.min(buffer.length, length - numReadSoFar);
76 | int numReadThisLoop = expectedBytesStream.read(buffer, 0, maxToRead);
77 | if (numReadThisLoop == -1) {
78 | throw new MismatchException("EOF reached in expectedBytesStream");
79 | }
80 | for (int matchCount = 0; matchCount < numReadThisLoop; matchCount++) {
81 | if (buffer[matchCount] != dataToWrite[offset + numReadSoFar + matchCount]) {
82 | throw new MismatchException("Data does not match");
83 | }
84 | }
85 | numReadSoFar += numReadThisLoop;
86 | }
87 | }
88 |
89 | @Override
90 | public void close() throws IOException {
91 | expectedBytesStream.close();
92 | }
93 |
94 | /**
95 | * Expects the end-of-file to be reached in the associated {@link InputStream}.
96 | * @throws IOException if the end-of-file has not yet been reached in the associated
97 | * {@link InputStream}
98 | */
99 | public void expectEof() throws IOException {
100 | if (expectedBytesStream.read() != -1) {
101 | throw new MismatchException("EOF not reached in expectedBytesStream");
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/MinimalCentralDirectoryMetadata.java:
--------------------------------------------------------------------------------
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 | /**
18 | * Trivial struct containing the critical data about the central directory: the number of entries
19 | * it contains, the position within the file at which it starts, and its length.
20 | */
21 | class MinimalCentralDirectoryMetadata {
22 | /**
23 | * The number of entries in the central directory.
24 | */
25 | private final int numEntriesInCentralDirectory;
26 |
27 | /**
28 | * The file offset of the first byte of the central directory.
29 | */
30 | private final long offsetOfCentralDirectory;
31 |
32 | /**
33 | * The length of the central directory, in bytes.
34 | */
35 | private final long lengthOfCentralDirectory;
36 |
37 | /**
38 | * Constructs a new metadata object with the specified values
39 | * @param numEntriesInCentralDirectory the number of entries in the central directory
40 | * @param offsetOfCentralDirectory the file offset of the first byte of the central directory
41 | * @param lengthOfCentralDirectory the length of the central directory, in bytes
42 | */
43 | MinimalCentralDirectoryMetadata(
44 | int numEntriesInCentralDirectory,
45 | long offsetOfCentralDirectory,
46 | long lengthOfCentralDirectory) {
47 | this.numEntriesInCentralDirectory = numEntriesInCentralDirectory;
48 | this.offsetOfCentralDirectory = offsetOfCentralDirectory;
49 | this.lengthOfCentralDirectory = lengthOfCentralDirectory;
50 | }
51 |
52 | /**
53 | * Returns the number of entries in the central directory.
54 | * @return as described
55 | */
56 | public final int getNumEntriesInCentralDirectory() {
57 | return numEntriesInCentralDirectory;
58 | }
59 |
60 | /**
61 | * Returns the file offset of the first byte of the central directory.
62 | * @return as described
63 | */
64 | public final long getOffsetOfCentralDirectory() {
65 | return offsetOfCentralDirectory;
66 | }
67 |
68 | /**
69 | * Returns the length of the central directory, in bytes.
70 | * @return as described
71 | */
72 | public final long getLengthOfCentralDirectory() {
73 | return lengthOfCentralDirectory;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/MinimalZipArchive.java:
--------------------------------------------------------------------------------
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 com.google.archivepatcher.shared.RandomAccessFileInputStream;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.util.ArrayList;
22 | import java.util.Collections;
23 | import java.util.Comparator;
24 | import java.util.List;
25 | import java.util.zip.ZipException;
26 |
27 | /**
28 | * A simplified, structural representation of a zip or zip-like (jar, apk, etc) archive. The class
29 | * provides the minimum structural information needed for patch generation and is not suitable as a
30 | * general zip-processing library. In particular, there is little or no verification that any of the
31 | * zip structure is correct or sane; it assumed that the input is sane.
32 | */
33 | public class MinimalZipArchive {
34 |
35 | /**
36 | * Sorts {@link MinimalZipEntry} objects by {@link MinimalZipEntry#getFileOffsetOfLocalEntry()} in
37 | * ascending order.
38 | */
39 | private static final Comparator LOCAL_ENTRY_OFFSET_COMAPRATOR =
40 | new Comparator() {
41 | @Override
42 | public int compare(MinimalZipEntry o1, MinimalZipEntry o2) {
43 | return Long.compare(o1.getFileOffsetOfLocalEntry(), o2.getFileOffsetOfLocalEntry());
44 | }
45 | };
46 |
47 | /**
48 | * Generate a listing of all of the files in a zip archive in file order and return it. Each entry
49 | * is a {@link MinimalZipEntry}, which has just enough information to generate a patch.
50 | * @param file the zip file to read
51 | * @return such a listing
52 | * @throws IOException if anything goes wrong while reading
53 | */
54 | public static List listEntries(File file) throws IOException {
55 | try (RandomAccessFileInputStream in = new RandomAccessFileInputStream(file)) {
56 | return listEntriesInternal(in);
57 | }
58 | }
59 |
60 | /**
61 | * Internal implementation of {@link #listEntries(File)}.
62 | * @param in the input stream to read from
63 | * @return see {@link #listEntries(File)}
64 | * @throws IOException if anything goes wrong while reading
65 | */
66 | private static List listEntriesInternal(RandomAccessFileInputStream in)
67 | throws IOException {
68 | // Step 1: Locate the end-of-central-directory record header.
69 | long offsetOfEocd = MinimalZipParser.locateStartOfEocd(in, 32768);
70 | if (offsetOfEocd == -1) {
71 | // Archive is weird, abort.
72 | throw new ZipException("EOCD record not found in last 32k of archive, giving up");
73 | }
74 |
75 | // Step 2: Parse the end-of-central-directory data to locate the central directory itself
76 | in.setRange(offsetOfEocd, in.length() - offsetOfEocd);
77 | MinimalCentralDirectoryMetadata centralDirectoryMetadata = MinimalZipParser.parseEocd(in);
78 |
79 | // Step 3: Extract a list of all central directory entries (contiguous data stream)
80 | in.setRange(
81 | centralDirectoryMetadata.getOffsetOfCentralDirectory(),
82 | centralDirectoryMetadata.getLengthOfCentralDirectory());
83 | List minimalZipEntries =
84 | new ArrayList(centralDirectoryMetadata.getNumEntriesInCentralDirectory());
85 | for (int x = 0; x < centralDirectoryMetadata.getNumEntriesInCentralDirectory(); x++) {
86 | minimalZipEntries.add(MinimalZipParser.parseCentralDirectoryEntry(in));
87 | }
88 |
89 | // Step 4: Sort the entries in file order, not central directory order.
90 | Collections.sort(minimalZipEntries, LOCAL_ENTRY_OFFSET_COMAPRATOR);
91 |
92 | // Step 5: Seek out each local entry and calculate the offset of the compressed data within
93 | for (int x = 0; x < minimalZipEntries.size(); x++) {
94 | MinimalZipEntry entry = minimalZipEntries.get(x);
95 | long offsetOfNextEntry;
96 | if (x < minimalZipEntries.size() - 1) {
97 | // Don't allow reading past the start of the next entry, for sanity.
98 | offsetOfNextEntry = minimalZipEntries.get(x + 1).getFileOffsetOfLocalEntry();
99 | } else {
100 | // Last entry. Don't allow reading into the central directory, for sanity.
101 | offsetOfNextEntry = centralDirectoryMetadata.getOffsetOfCentralDirectory();
102 | }
103 | long rangeLength = offsetOfNextEntry - entry.getFileOffsetOfLocalEntry();
104 | in.setRange(entry.getFileOffsetOfLocalEntry(), rangeLength);
105 | long relativeDataOffset = MinimalZipParser.parseLocalEntryAndGetCompressedDataOffset(in);
106 | entry.setFileOffsetOfCompressedData(entry.getFileOffsetOfLocalEntry() + relativeDataOffset);
107 | }
108 |
109 | // Done!
110 | return minimalZipEntries;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/MismatchException.java:
--------------------------------------------------------------------------------
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.IOException;
18 |
19 | /**
20 | * Thrown when data does not match expected values.
21 | */
22 | @SuppressWarnings("serial")
23 | public class MismatchException extends IOException {
24 | /**
25 | * Construct an exception with the specified message
26 | * @param message the message
27 | */
28 | public MismatchException(String message) {
29 | super(message);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/PatchWriter.java:
--------------------------------------------------------------------------------
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 com.google.archivepatcher.shared.JreDeflateParameters;
18 | import com.google.archivepatcher.shared.PatchConstants;
19 | import com.google.archivepatcher.shared.TypedRange;
20 | import java.io.BufferedInputStream;
21 | import java.io.DataOutputStream;
22 | import java.io.File;
23 | import java.io.FileInputStream;
24 | import java.io.IOException;
25 | import java.io.OutputStream;
26 |
27 | /**
28 | * Writes patches.
29 | */
30 | public class PatchWriter {
31 | /**
32 | * The patch plan.
33 | */
34 | private final PreDiffPlan plan;
35 |
36 | /**
37 | * The expected size of the delta-friendly old file, provided as a convenience for the patch
38 | * applier to reserve space on the filesystem for applying the patch.
39 | */
40 | private final long deltaFriendlyOldFileSize;
41 |
42 | /**
43 | * The expected size of the delta-friendly new file, provided for forward compatibility.
44 | */
45 | private final long deltaFriendlyNewFileSize;
46 |
47 | /**
48 | * The delta that transforms the old delta-friendly file into the new delta-friendly file.
49 | */
50 | private final File deltaFile;
51 |
52 | /**
53 | * Creates a new patch writer.
54 | *
55 | * @param plan the patch plan
56 | * @param deltaFriendlyOldFileSize the expected size of the delta-friendly old file, provided as a
57 | * convenience for the patch applier to reserve space on the filesystem for
58 | * applying the patch
59 | * @param deltaFriendlyNewFileSize the expected size of the delta-friendly new file, provided for
60 | * forward compatibility
61 | * @param deltaFile the delta that transforms the old delta-friendly file into the new
62 | * delta-friendly file
63 | */
64 | public PatchWriter(
65 | PreDiffPlan plan,
66 | long deltaFriendlyOldFileSize,
67 | long deltaFriendlyNewFileSize,
68 | File deltaFile) {
69 | this.plan = plan;
70 | this.deltaFriendlyOldFileSize = deltaFriendlyOldFileSize;
71 | this.deltaFriendlyNewFileSize = deltaFriendlyNewFileSize;
72 | this.deltaFile = deltaFile;
73 | }
74 |
75 | /**
76 | * Write a v1-style patch to the specified output stream.
77 | * @param out the stream to write the patch to
78 | * @throws IOException if anything goes wrong
79 | */
80 | public void writeV1Patch(OutputStream out) throws IOException {
81 | // Use DataOutputStream for ease of writing. This is deliberately left open, as closing it would
82 | // close the output stream that was passed in and that is not part of the method's documented
83 | // behavior.
84 | @SuppressWarnings("resource")
85 | DataOutputStream dataOut = new DataOutputStream(out);
86 |
87 | dataOut.write(PatchConstants.IDENTIFIER.getBytes("US-ASCII"));
88 | dataOut.writeInt(0); // Flags (reserved)
89 | dataOut.writeLong(deltaFriendlyOldFileSize);
90 |
91 | // Write out all the delta-friendly old file uncompression instructions
92 | dataOut.writeInt(plan.getOldFileUncompressionPlan().size());
93 | for (TypedRange range : plan.getOldFileUncompressionPlan()) {
94 | dataOut.writeLong(range.getOffset());
95 | dataOut.writeLong(range.getLength());
96 | }
97 |
98 | // Write out all the delta-friendly new file recompression instructions
99 | dataOut.writeInt(plan.getDeltaFriendlyNewFileRecompressionPlan().size());
100 | for (TypedRange range : plan.getDeltaFriendlyNewFileRecompressionPlan()) {
101 | dataOut.writeLong(range.getOffset());
102 | dataOut.writeLong(range.getLength());
103 | // Write the deflate information
104 | dataOut.write(PatchConstants.CompatibilityWindowId.DEFAULT_DEFLATE.patchValue);
105 | dataOut.write(range.getMetadata().level);
106 | dataOut.write(range.getMetadata().strategy);
107 | dataOut.write(range.getMetadata().nowrap ? 1 : 0);
108 | }
109 |
110 | // Now the delta section
111 | // First write the number of deltas present in the patch. In v1, there is always exactly one
112 | // delta, and it is for the entire input; in future versions there may be multiple deltas, of
113 | // arbitrary types.
114 | dataOut.writeInt(1);
115 | // In v1 the delta format is always bsdiff, so write it unconditionally.
116 | dataOut.write(PatchConstants.DeltaFormat.BSDIFF.patchValue);
117 |
118 | // Write the working ranges. In v1 these are always the entire contents of the delta-friendly
119 | // old file and the delta-friendly new file. These are for forward compatibility with future
120 | // versions that may allow deltas of arbitrary formats to be mapped to arbitrary ranges.
121 | dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly old file
122 | dataOut.writeLong(deltaFriendlyOldFileSize); // i.e., length of the working range in old
123 | dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly new file
124 | dataOut.writeLong(deltaFriendlyNewFileSize); // i.e., length of the working range in new
125 |
126 | // Finally, the length of the delta and the delta itself.
127 | dataOut.writeLong(deltaFile.length());
128 | try (FileInputStream deltaFileIn = new FileInputStream(deltaFile);
129 | BufferedInputStream deltaIn = new BufferedInputStream(deltaFileIn)) {
130 | byte[] buffer = new byte[32768];
131 | int numRead = 0;
132 | while ((numRead = deltaIn.read(buffer)) >= 0) {
133 | dataOut.write(buffer, 0, numRead);
134 | }
135 | }
136 | dataOut.flush();
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/QualifiedRecommendation.java:
--------------------------------------------------------------------------------
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 | /**
18 | * A fully qualified recommendation, consisting of an {@link MinimalZipEntry} from the old file,
19 | * a {@link MinimalZipEntry} from the new file, a {@link Recommendation} for how to proceed and a
20 | * {@link RecommendationReason} for that recommendation.
21 | */
22 | public class QualifiedRecommendation {
23 | /**
24 | * The entry in the old file.
25 | */
26 | private final MinimalZipEntry oldEntry;
27 |
28 | /**
29 | * The entry in the new file.
30 | */
31 | private final MinimalZipEntry newEntry;
32 |
33 | /**
34 | * The recommendation for how to proceed on the pair of entries.
35 | */
36 | private final Recommendation recommendation;
37 |
38 | /**
39 | * The reason for the recommendation.
40 | */
41 | private final RecommendationReason reason;
42 |
43 | /**
44 | * Construct a new qualified recommendation with the specified data.
45 | * @param oldEntry the entry in the old file
46 | * @param newEntry the entry in the new file
47 | * @param recommendation the recommendation for this tuple of entries
48 | * @param reason the reason for the recommendation
49 | */
50 | public QualifiedRecommendation(
51 | MinimalZipEntry oldEntry,
52 | MinimalZipEntry newEntry,
53 | Recommendation recommendation,
54 | RecommendationReason reason) {
55 | super();
56 | this.oldEntry = oldEntry;
57 | this.newEntry = newEntry;
58 | this.recommendation = recommendation;
59 | this.reason = reason;
60 | }
61 |
62 | /**
63 | * Returns the entry in the old file.
64 | * @return as described
65 | */
66 | public MinimalZipEntry getOldEntry() {
67 | return oldEntry;
68 | }
69 |
70 | /**
71 | * Returns the entry in the new file.
72 | * @return as described
73 | */
74 | public MinimalZipEntry getNewEntry() {
75 | return newEntry;
76 | }
77 |
78 | /**
79 | * Returns the recommendation for how to proceed for this tuple of entries.
80 | * @return as described
81 | */
82 | public Recommendation getRecommendation() {
83 | return recommendation;
84 | }
85 |
86 | /**
87 | * Returns the reason for the recommendation.
88 | * @return as described
89 | */
90 | public RecommendationReason getReason() {
91 | return reason;
92 | }
93 |
94 | @Override
95 | public int hashCode() {
96 | final int prime = 31;
97 | int result = 1;
98 | result = prime * result + ((newEntry == null) ? 0 : newEntry.hashCode());
99 | result = prime * result + ((oldEntry == null) ? 0 : oldEntry.hashCode());
100 | result = prime * result + ((reason == null) ? 0 : reason.hashCode());
101 | result = prime * result + ((recommendation == null) ? 0 : recommendation.hashCode());
102 | return result;
103 | }
104 |
105 | @Override
106 | public boolean equals(Object obj) {
107 | if (this == obj) {
108 | return true;
109 | }
110 | if (obj == null) {
111 | return false;
112 | }
113 | if (getClass() != obj.getClass()) {
114 | return false;
115 | }
116 | QualifiedRecommendation other = (QualifiedRecommendation) obj;
117 | if (newEntry == null) {
118 | if (other.newEntry != null) {
119 | return false;
120 | }
121 | } else if (!newEntry.equals(other.newEntry)) {
122 | return false;
123 | }
124 | if (oldEntry == null) {
125 | if (other.oldEntry != null) {
126 | return false;
127 | }
128 | } else if (!oldEntry.equals(other.oldEntry)) {
129 | return false;
130 | }
131 | if (reason != other.reason) {
132 | return false;
133 | }
134 | if (recommendation != other.recommendation) {
135 | return false;
136 | }
137 | return true;
138 | }
139 |
140 | @Override
141 | public String toString() {
142 | return "QualifiedRecommendation [oldEntry="
143 | + oldEntry.getFileName()
144 | + ", newEntry="
145 | + newEntry.getFileName()
146 | + ", recommendation="
147 | + recommendation
148 | + ", reason="
149 | + reason
150 | + "]";
151 | }
152 |
153 | }
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/Recommendation.java:
--------------------------------------------------------------------------------
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 | /**
18 | * Recommendations for how to uncompress entries in old and new archives.
19 | */
20 | public enum Recommendation {
21 |
22 | /**
23 | * Uncompress only the old entry.
24 | */
25 | UNCOMPRESS_OLD(true, false),
26 |
27 | /**
28 | * Uncompress only the new entry.
29 | */
30 | UNCOMPRESS_NEW(false, true),
31 |
32 | /**
33 | * Uncompress both the old and new entries.
34 | */
35 | UNCOMPRESS_BOTH(true, true),
36 |
37 | /**
38 | * Uncompress neither entry.
39 | */
40 | UNCOMPRESS_NEITHER(false, false);
41 |
42 | /**
43 | * True if the old entry should be uncompressed.
44 | */
45 | public final boolean uncompressOldEntry;
46 |
47 | /**
48 | * True if the new entry should be uncompressed.
49 | */
50 | public final boolean uncompressNewEntry;
51 |
52 | /**
53 | * Constructs a new recommendation with the specified behaviors.
54 | * @param uncompressOldEntry true if the old entry should be uncompressed
55 | * @param uncompressNewEntry true if the new entry should be uncompressed
56 | */
57 | private Recommendation(boolean uncompressOldEntry, boolean uncompressNewEntry) {
58 | this.uncompressOldEntry = uncompressOldEntry;
59 | this.uncompressNewEntry = uncompressNewEntry;
60 | }
61 | }
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/RecommendationModifier.java:
--------------------------------------------------------------------------------
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.List;
19 |
20 | /**
21 | * Provides a mechanism to review and possibly modify the {@link QualifiedRecommendation}s that will
22 | * be used to derive a {@link PreDiffPlan}.
23 | */
24 | public interface RecommendationModifier {
25 | /**
26 | * Given a list of {@link QualifiedRecommendation} objects, returns a list of the same type that
27 | * has been arbitrarily adjusted as desired by the implementation. Implementations must return a
28 | * list of recommendations that contains the same tuples of (oldEntry, newEntry) but may change
29 | * the results of {@link QualifiedRecommendation#getRecommendation()} and {@link
30 | * QualifiedRecommendation#getReason()} to any sane values.
31 | *
32 | * @param oldFile the old file that is being diffed
33 | * @param newFile the new file that is being diffed
34 | * @param originalRecommendations the original recommendations
35 | * @return the updated list of recommendations
36 | */
37 | public List getModifiedRecommendations(
38 | File oldFile, File newFile, List originalRecommendations);
39 | }
40 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/RecommendationReason.java:
--------------------------------------------------------------------------------
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 | /**
18 | * Reasons for a corresponding {@link Recommendation}.
19 | */
20 | public enum RecommendationReason {
21 | /**
22 | * The entry in the new file is compressed using deflate in a way that cannot be reliably
23 | * reproduced. This could be caused by using an unsupported version of zlib.
24 | */
25 | DEFLATE_UNSUITABLE,
26 | /**
27 | * The entry in the new file is compressed in a way that cannot be reliably reproduced (or one of
28 | * the entries is compressed using something other than deflate, but this is very uncommon).
29 | */
30 | UNSUITABLE,
31 |
32 | /**
33 | * Both the old and new entries are already uncompressed.
34 | */
35 | BOTH_ENTRIES_UNCOMPRESSED,
36 |
37 | /**
38 | * An entry that was uncompressed in the old file is compressed in the new file.
39 | */
40 | UNCOMPRESSED_CHANGED_TO_COMPRESSED,
41 |
42 | /**
43 | * An entry that was compressed in the old file is uncompressed in the new file.
44 | */
45 | COMPRESSED_CHANGED_TO_UNCOMPRESSED,
46 |
47 | /**
48 | * The compressed bytes in the old file do not match the compressed bytes in the new file.
49 | */
50 | COMPRESSED_BYTES_CHANGED,
51 |
52 | /** The compressed bytes in the old file are identical to the compressed bytes in the new file. */
53 | COMPRESSED_BYTES_IDENTICAL,
54 |
55 | /**
56 | * A resource constraint prohibits touching the old entry, the new entry, or both. For example,
57 | * there may be a limit on the total amount of temp space that will be available for applying a
58 | * patch or a limit on the total amount of CPU time that can be expended on recompression when
59 | * applying a patch, etc.
60 | */
61 | RESOURCE_CONSTRAINED;
62 | }
63 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/TempFileHolder.java:
--------------------------------------------------------------------------------
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.Closeable;
18 | import java.io.File;
19 | import java.io.IOException;
20 |
21 | /**
22 | * A closeable container for a temp file that deletes itself on {@link #close()}. This is convenient
23 | * for try-with-resources constructs that need to use temp files in scope.
24 | */
25 | public class TempFileHolder implements Closeable {
26 | /**
27 | * The file that is wrapped by this holder.
28 | */
29 | public final File file;
30 |
31 | /**
32 | * Create a new temp file and wrap it in an instance of this class. The file is automatically
33 | * scheduled for deletion on JVM termination, so it is a serious error to rely on this file path
34 | * being a durable artifact.
35 | * @throws IOException if unable to create the file
36 | */
37 | public TempFileHolder() throws IOException {
38 | file = File.createTempFile("archive_patcher", "tmp");
39 | file.deleteOnExit();
40 | }
41 |
42 | @Override
43 | public void close() throws IOException {
44 | file.delete();
45 | }
46 | }
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/BsDiffDeltaGenerator.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import com.google.archivepatcher.generator.DeltaGenerator;
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 |
22 | /**
23 | * An implementation of {@link DeltaGenerator} that uses {@link BsDiffPatchWriter} to write a
24 | * bsdiff patch that represents the delta between given inputs.
25 | */
26 | public class BsDiffDeltaGenerator implements DeltaGenerator {
27 | /**
28 | * The minimum match length to use for bsdiff.
29 | */
30 | private static final int MATCH_LENGTH_BYTES = 16;
31 |
32 | @Override
33 | public void generateDelta(File oldBlob, File newBlob, OutputStream deltaOut)
34 | throws IOException, InterruptedException {
35 | BsDiffPatchWriter.generatePatch(oldBlob, newBlob, deltaOut, MATCH_LENGTH_BYTES);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/BsUtil.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | /**
22 | * Utility functions to be shared between BsDiff and BsPatch.
23 | */
24 | class BsUtil {
25 | /**
26 | * Mask to determine whether a long written by {@link #writeFormattedLong(long, OutputStream)}
27 | * is negative.
28 | */
29 | private static final long NEGATIVE_MASK = 1L << 63;
30 |
31 | /**
32 | * Writes a 64-bit signed integer to the specified {@link OutputStream}. The least significant
33 | * byte is written first and the most significant byte is written last.
34 | * @param value the value to write
35 | * @param outputStream the stream to write to
36 | */
37 | static void writeFormattedLong(final long value, OutputStream outputStream)
38 | throws IOException {
39 | long y = value;
40 | if (y < 0) {
41 | y = (-y) | NEGATIVE_MASK;
42 | }
43 |
44 | for (int i = 0; i < 8; ++i) {
45 | outputStream.write((byte) (y & 0xff));
46 | y >>>= 8;
47 | }
48 | }
49 |
50 | /**
51 | * Reads a 64-bit signed integer written by {@link #writeFormattedLong(long, OutputStream)} from
52 | * the specified {@link InputStream}.
53 | * @param inputStream the stream to read from
54 | */
55 | static long readFormattedLong(InputStream inputStream) throws IOException {
56 | long result = 0;
57 | for (int bitshift = 0; bitshift < 64; bitshift += 8) {
58 | result |= ((long) inputStream.read()) << bitshift;
59 | }
60 |
61 | if ((result - NEGATIVE_MASK) > 0) {
62 | result = (result & ~NEGATIVE_MASK) * -1;
63 | }
64 | return result;
65 | }
66 |
67 | /**
68 | * Provides functional equivalent to C/C++ lexicographical_compare. Warning: this calls {@link
69 | * RandomAccessObject#seek(long)}, so the internal state of the data objects will be modified.
70 | *
71 | * @param data1 first byte array
72 | * @param start1 index in the first array at which to start comparing
73 | * @param length1 length of first byte array
74 | * @param data2 second byte array
75 | * @param start2 index in the second array at which to start comparing
76 | * @param length2 length of second byte array
77 | * @return result of lexicographical compare: negative if the first difference has a lower value
78 | * in the first array, positive if the first difference has a lower value in the second array.
79 | * If both arrays compare equal until one of them ends, the shorter sequence is
80 | * lexicographically less than the longer one (i.e., it returns len(first array) -
81 | * len(second array)).
82 | */
83 | static int lexicographicalCompare(
84 | final RandomAccessObject data1,
85 | final int start1,
86 | final int length1,
87 | final RandomAccessObject data2,
88 | final int start2,
89 | final int length2)
90 | throws IOException {
91 | int bytesLeft = Math.min(length1, length2);
92 |
93 | data1.seek(start1);
94 | data2.seek(start2);
95 | while (bytesLeft-- > 0) {
96 | final int i1 = data1.readUnsignedByte();
97 | final int i2 = data2.readUnsignedByte();
98 |
99 | if (i1 != i2) {
100 | return i1 - i2;
101 | }
102 | }
103 |
104 | return length1 - length2;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/Matcher.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import java.io.IOException;
18 |
19 | /**
20 | * Helper class which iterates through |newData| finding the longest valid exact matches between
21 | * |newData| and |oldData|. The interface exists for the sake of testing.
22 | */
23 | interface Matcher {
24 | /**
25 | * Determine the range for the next match, and store it in member state.
26 | * @return a {@link NextMatch} describing the result
27 | */
28 | NextMatch next() throws IOException, InterruptedException;
29 |
30 | /**
31 | * Contains a boolean which indicates whether a match was found, the old position (if a match was
32 | * found), and the new position (if a match was found).
33 | */
34 | static class NextMatch {
35 | final boolean didFindMatch;
36 | final int oldPosition;
37 | final int newPosition;
38 |
39 | static NextMatch of(boolean didFindMatch, int oldPosition, int newPosition) {
40 | return new NextMatch(didFindMatch, oldPosition, newPosition);
41 | }
42 |
43 | private NextMatch(boolean didFindMatch, int oldPosition, int newPosition) {
44 | this.didFindMatch = didFindMatch;
45 | this.oldPosition = oldPosition;
46 | this.newPosition = newPosition;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObjectFactory.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import com.google.archivepatcher.generator.bsdiff.RandomAccessObject.RandomAccessByteArrayObject;
18 | import com.google.archivepatcher.generator.bsdiff.RandomAccessObject.RandomAccessFileObject;
19 | import com.google.archivepatcher.generator.bsdiff.RandomAccessObject.RandomAccessMmapObject;
20 |
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.io.RandomAccessFile;
24 |
25 | /**
26 | * A factory for creating instances of {@link RandomAccessObject}. BsDiff needs to store some
27 | * ancillary data of size proportional to the binary to be patched. This allows abstraction of the
28 | * allocation so that BsDiff can run either entirely in-memory (faster) or with file-backed swap
29 | * (handles bigger inputs without consuming inordinate amounts of memory).
30 | */
31 | public interface RandomAccessObjectFactory {
32 | public RandomAccessObject create(int size) throws IOException;
33 |
34 | /**
35 | * A factory that produces {@link RandomAccessFileObject} instances backed by temp files.
36 | */
37 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
38 | // really be the responsibility of RandomAccessObject.
39 | public static final class RandomAccessFileObjectFactory implements RandomAccessObjectFactory {
40 | private static final String FILE_NAME_PREFIX = "wavsprafof";
41 | private final String mMode;
42 |
43 | /**
44 | * Factory for a RandomAccessFileObject.
45 | * @param mode the file mode string ("r", "w", "rw", etc - see documentation for
46 | * {@link RandomAccessFile})
47 | */
48 | public RandomAccessFileObjectFactory(String mode) {
49 | mMode = mode;
50 | }
51 |
52 | /**
53 | * Creates a temp file, and returns a {@link RandomAccessFile} wrapped in a
54 | * {@link RandomAccessFileObject} representing the new temp file. The temp file does not need to
55 | * explicitly be managed (deleted) by the caller, as long as the caller ensures
56 | * {@link RandomAccessObject#close()} is called when the object is no longer needed.
57 | */
58 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
59 | // really be the responsibility of RandomAccessObject.
60 | @Override
61 | public RandomAccessObject create(int size) throws IOException {
62 | return new RandomAccessObject.RandomAccessFileObject(
63 | File.createTempFile(FILE_NAME_PREFIX, "temp"), mMode, true);
64 | }
65 | }
66 |
67 | /**
68 | * A factory that produces {@link RandomAccessByteArrayObject} instances backed by memory.
69 | */
70 | public static final class RandomAccessByteArrayObjectFactory
71 | implements RandomAccessObjectFactory {
72 | @Override
73 | public RandomAccessObject create(int size) {
74 | return new RandomAccessObject.RandomAccessByteArrayObject(size);
75 | }
76 | }
77 |
78 | /**
79 | * A factory that produces {@link RandomAccessMmapObject} instances backed by temp files..
80 | */
81 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
82 | // really be the responsibility of RandomAccessObject.
83 | public static final class RandomAccessMmapObjectFactory implements RandomAccessObjectFactory {
84 | private static final String FILE_NAME_PREFIX = "wavsprafof";
85 | private String mMode;
86 |
87 | /**
88 | * Factory for a RandomAccessMmapObject.
89 | * @param mode the file mode string ("r", "w", "rw", etc - see documentation for
90 | * {@link RandomAccessFile})
91 | */
92 | public RandomAccessMmapObjectFactory(String mode) {
93 | mMode = mode;
94 | }
95 |
96 | /**
97 | * Creates a temp file, and returns a {@link RandomAccessFile} wrapped in a
98 | * {@link RandomAccessMmapObject} representing the new temp file. The temp file does not need to
99 | * explicitly be managed (deleted) by the caller, as long as the caller ensures
100 | * {@link RandomAccessObject#close()} is called when the object is no longer needed.
101 | */
102 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
103 | // really be the responsibility of RandomAccessObject.
104 | @Override
105 | public RandomAccessObject create(int size) throws IOException {
106 | return new RandomAccessObject.RandomAccessMmapObject(FILE_NAME_PREFIX, mMode, size);
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/SuffixSorter.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import java.io.IOException;
18 |
19 | /**
20 | * An algorithm that performs a suffix sort on a given input and returns a suffix array.
21 | * See https://en.wikipedia.org/wiki/Suffix_array
22 | */
23 | public interface SuffixSorter {
24 |
25 | /**
26 | * Perform a "suffix sort". Note: the returned {@link RandomAccessObject} should be closed by the
27 | * caller.
28 | *
29 | * @param data the data to sort
30 | * @return the suffix array, as a {@link RandomAccessObject}
31 | * @throws IOException if unable to read data
32 | * @throws InterruptedException if any thread interrupts this thread
33 | */
34 | RandomAccessObject suffixSort(RandomAccessObject data) throws IOException, InterruptedException;
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/similarity/Crc32SimilarityFinder.java:
--------------------------------------------------------------------------------
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.similarity;
16 |
17 | import com.google.archivepatcher.generator.MinimalZipEntry;
18 |
19 | import java.io.File;
20 | import java.util.Collection;
21 | import java.util.Collections;
22 | import java.util.HashMap;
23 | import java.util.LinkedList;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | /**
28 | * Detects identical files on the basis of the CRC32 of uncompressed content. All entries that have
29 | * the same CRC32 will be identified as similar (and presumably are identical, in the absence of
30 | * hash collisions).
31 | */
32 | public class Crc32SimilarityFinder extends SimilarityFinder {
33 |
34 | /**
35 | * All entries in the base archive, organized by CRC32.
36 | */
37 | private final Map> baseEntriesByCrc32 = new HashMap<>();
38 |
39 | /**
40 | * Constructs a new similarity finder with the specified parameters.
41 | * @param baseArchive the base archive that contains the entries to be searched
42 | * @param baseEntries the entries in the base archive that are eligible to be searched
43 | */
44 | public Crc32SimilarityFinder(File baseArchive, Collection baseEntries) {
45 | super(baseArchive, baseEntries);
46 | for (MinimalZipEntry oldEntry : baseEntries) {
47 | long crc32 = oldEntry.getCrc32OfUncompressedData();
48 | List entriesForCrc32 = baseEntriesByCrc32.get(crc32);
49 | if (entriesForCrc32 == null) {
50 | entriesForCrc32 = new LinkedList<>();
51 | baseEntriesByCrc32.put(crc32, entriesForCrc32);
52 | }
53 | entriesForCrc32.add(oldEntry);
54 | }
55 | }
56 |
57 | @Override
58 | public List findSimilarFiles(File newArchive, MinimalZipEntry newEntry) {
59 | List matchedEntries =
60 | baseEntriesByCrc32.get(newEntry.getCrc32OfUncompressedData());
61 | if (matchedEntries == null) {
62 | return Collections.emptyList();
63 | }
64 | return Collections.unmodifiableList(matchedEntries);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/generator/src/main/java/com/google/archivepatcher/generator/similarity/SimilarityFinder.java:
--------------------------------------------------------------------------------
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.similarity;
16 |
17 | import java.io.File;
18 | import java.util.Collection;
19 | import java.util.List;
20 |
21 | import com.google.archivepatcher.generator.MinimalZipEntry;
22 |
23 | /**
24 | * A class that analyzes an archive to find files similar to a specified file.
25 | */
26 | public abstract class SimilarityFinder {
27 |
28 | /**
29 | * The base archive that contains the entries to be searched.
30 | */
31 | protected final File baseArchive;
32 |
33 | /**
34 | * The entries in the base archive that are eligible to be searched.
35 | */
36 | protected final Collection baseEntries;
37 |
38 | /**
39 | * Create a new instance to check for similarity of arbitrary files against the specified entries
40 | * in the specified archive.
41 | * @param baseArchive the base archive that contains the entries to be scored against
42 | * @param baseEntries the entries in the base archive that are eligible to be scored against.
43 | */
44 | public SimilarityFinder(File baseArchive, Collection baseEntries) {
45 | this.baseArchive = baseArchive;
46 | this.baseEntries = baseEntries;
47 | }
48 |
49 | /**
50 | * Searches for files similar to the specified entry in the specified new archive against all of
51 | * the available entries in the base archive.
52 | * @param newArchive the new archive that contains the new entry
53 | * @param newEntry the new entry to compare against the entries in the base archive
54 | * @return a {@link List} of {@link MinimalZipEntry} entries (possibly empty but never null) from
55 | * the base archive that are similar to the new archive; if the list has more than one entry, the
56 | * entries should be in order from most similar to least similar.
57 | */
58 | public abstract List findSimilarFiles(File newArchive, MinimalZipEntry newEntry);
59 | }
60 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/ByteArrayHolderTest.java:
--------------------------------------------------------------------------------
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 org.junit.Assert;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.junit.runners.JUnit4;
21 |
22 | /**
23 | * Tests for {@link ByteArrayHolder}.
24 | */
25 | @RunWith(JUnit4.class)
26 | @SuppressWarnings("javadoc")
27 | public class ByteArrayHolderTest {
28 |
29 | @Test
30 | public void testGetters() {
31 | byte[] data = "hello world".getBytes();
32 | ByteArrayHolder byteArrayHolder = new ByteArrayHolder(data);
33 | Assert.assertSame(data, byteArrayHolder.getData());
34 | }
35 |
36 | @Test
37 | public void testHashCode() {
38 | byte[] data1a = "hello world".getBytes();
39 | byte[] data1b = new String("hello world").getBytes();
40 | byte[] data2 = "hello another world".getBytes();
41 | ByteArrayHolder rawText1a = new ByteArrayHolder(data1a);
42 | ByteArrayHolder rawText1b = new ByteArrayHolder(data1b);
43 | Assert.assertEquals(rawText1a.hashCode(), rawText1b.hashCode());
44 | ByteArrayHolder rawText2 = new ByteArrayHolder(data2);
45 | Assert.assertNotEquals(rawText1a.hashCode(), rawText2.hashCode());
46 | ByteArrayHolder rawText3 = new ByteArrayHolder(null);
47 | Assert.assertNotEquals(rawText1a.hashCode(), rawText3.hashCode());
48 | Assert.assertNotEquals(rawText2.hashCode(), rawText3.hashCode());
49 | }
50 |
51 | @Test
52 | public void testEquals() {
53 | byte[] data1a = "hello world".getBytes();
54 | byte[] data1b = new String("hello world").getBytes();
55 | byte[] data2 = "hello another world".getBytes();
56 | ByteArrayHolder rawText1a = new ByteArrayHolder(data1a);
57 | Assert.assertEquals(rawText1a, rawText1a);
58 | ByteArrayHolder rawText1b = new ByteArrayHolder(data1b);
59 | Assert.assertEquals(rawText1a, rawText1b);
60 | ByteArrayHolder rawText2 = new ByteArrayHolder(data2);
61 | Assert.assertNotEquals(rawText1a, rawText2);
62 | ByteArrayHolder rawText3 = new ByteArrayHolder(null);
63 | Assert.assertNotEquals(rawText1a, rawText3);
64 | Assert.assertNotEquals(rawText3, rawText1a);
65 | Assert.assertNotEquals(rawText1a, 42);
66 | Assert.assertNotEquals(rawText1a, null);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/FileByFileV1DeltaGeneratorTest.java:
--------------------------------------------------------------------------------
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 com.google.archivepatcher.shared.UnitTestZipArchive;
18 | import java.io.ByteArrayOutputStream;
19 | import org.junit.Assert;
20 | import org.junit.Test;
21 | import org.junit.runner.RunWith;
22 | import org.junit.runners.JUnit4;
23 |
24 | /**
25 | * Tests for {@link FileByFileV1DeltaGenerator}. This relies heavily on the correctness of {@link
26 | * PatchWriterTest}, which validates the patch writing process itself, {@link PreDiffPlannerTest},
27 | * which validates the decision making process for delta-friendly blobs, and {@link
28 | * PreDiffExecutorTest}, which validates the ability to create the delta-friendly blobs. The {@link
29 | * FileByFileV1DeltaGenerator} itself is relatively simple, combining all of these pieces
30 | * of functionality together to create a patch; so the tests here are just ensuring that a patch can
31 | * be produced.
32 | */
33 | @RunWith(JUnit4.class)
34 | @SuppressWarnings("javadoc")
35 | public class FileByFileV1DeltaGeneratorTest {
36 |
37 | @Test
38 | public void testGenerateDelta_BaseCase() throws Exception {
39 | // Simple test of generating a patch with no changes.
40 | FileByFileV1DeltaGenerator generator = new FileByFileV1DeltaGenerator();
41 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
42 | try (TempFileHolder oldArchive = new TempFileHolder();
43 | TempFileHolder newArchive = new TempFileHolder()) {
44 | UnitTestZipArchive.saveTestZip(oldArchive.file);
45 | UnitTestZipArchive.saveTestZip(newArchive.file);
46 | generator.generateDelta(oldArchive.file, newArchive.file, buffer);
47 | }
48 | byte[] result = buffer.toByteArray();
49 | Assert.assertTrue(result.length > 0);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/MatchingOutputStreamTest.java:
--------------------------------------------------------------------------------
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 org.junit.Before;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.junit.runners.JUnit4;
21 |
22 | import java.io.ByteArrayInputStream;
23 | import java.io.IOException;
24 |
25 | /**
26 | * Tests for {@link MatchingOutputStream}.
27 | */
28 | @RunWith(JUnit4.class)
29 | @SuppressWarnings("javadoc")
30 | public class MatchingOutputStreamTest {
31 | /**
32 | * The data to write to the stream.
33 | */
34 | private byte[] data;
35 |
36 | /**
37 | * Input for matching.
38 | */
39 | private ByteArrayInputStream inputStream;
40 |
41 | /**
42 | * The stream under test.
43 | */
44 | private MatchingOutputStream outputStream;
45 |
46 | @Before
47 | public void setup() {
48 | data = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
49 | inputStream = new ByteArrayInputStream(data);
50 | outputStream = new MatchingOutputStream(inputStream, 3 /* buffer size */);
51 | }
52 |
53 | @Test
54 | public void testWrite_OneByte() throws IOException {
55 | for (int x = 0; x < data.length; x++) {
56 | outputStream.write(data[x] & 0xff);
57 | }
58 | }
59 |
60 | @Test
61 | public void testWrite_WholeBuffer() throws IOException {
62 | outputStream.write(data);
63 | }
64 |
65 | @Test
66 | public void testWrite_WholeBuffer_RealisticCopyBuffer() throws IOException {
67 | outputStream = new MatchingOutputStream(inputStream, 32768); // realistic copy buffer size
68 | outputStream.write(data);
69 | }
70 |
71 | @Test
72 | public void testWrite_PartialBuffer() throws IOException {
73 | for (int x = 0; x < data.length; x++) {
74 | outputStream.write(data, x, 1);
75 | }
76 | }
77 |
78 | @Test
79 | public void testExpectEof() throws IOException {
80 | outputStream.write(data);
81 | outputStream.expectEof();
82 | }
83 |
84 | @Test(expected = MismatchException.class)
85 | public void testWrite_OneByte_MatchFail() throws IOException {
86 | outputStream.write(0);
87 | outputStream.write(77);
88 | }
89 |
90 | @Test(expected = MismatchException.class)
91 | public void testWrite_OneByte_StreamFail() throws IOException {
92 | // Write one byte more than the data match stream contains
93 | for (int x = 0; x <= data.length; x++) {
94 | outputStream.write(x);
95 | }
96 | }
97 |
98 | @Test(expected = MismatchException.class)
99 | public void testWrite_WholeBuffer_Fail() throws IOException {
100 | byte[] tweaked = new byte[] {0, 1, 2, 3, 4, 55, 6, 7, 8, 9};
101 | outputStream.write(tweaked);
102 | }
103 |
104 | @Test(expected = MismatchException.class)
105 | public void testWrite_PartialBuffer_Fail() throws IOException {
106 | byte[] tweaked = new byte[] {0, 1, 2, 3, 4, 55, 6, 7, 8, 9};
107 | outputStream.write(tweaked, 0, 8);
108 | }
109 |
110 | @Test(expected = MismatchException.class)
111 | public void testExpectEof_Fail() throws IOException {
112 | outputStream.write(data, 0, data.length - 1);
113 | outputStream.expectEof();
114 | }
115 |
116 | @Test(expected = MismatchException.class)
117 | public void testWrite_PastEndOfMatchStream() throws IOException {
118 | outputStream.write(data);
119 | outputStream.write(data);
120 | }
121 |
122 | @SuppressWarnings({"resource", "unused"})
123 | @Test(expected = IllegalArgumentException.class)
124 | public void testConstructor_BadMatchBufferLength() {
125 | new MatchingOutputStream(inputStream, 0);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/MinimalZipArchiveTest.java:
--------------------------------------------------------------------------------
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 com.google.archivepatcher.shared.UnitTestZipArchive;
18 | import com.google.archivepatcher.shared.UnitTestZipEntry;
19 |
20 | import org.junit.After;
21 | import org.junit.Assert;
22 | import org.junit.Before;
23 | import org.junit.Test;
24 | import org.junit.runner.RunWith;
25 | import org.junit.runners.JUnit4;
26 |
27 | import java.io.File;
28 | import java.io.FileOutputStream;
29 | import java.io.IOException;
30 | import java.util.List;
31 | import java.util.zip.CRC32;
32 |
33 | /**
34 | * Tests for {@link MinimalZipParser}.
35 | */
36 | @RunWith(JUnit4.class)
37 | @SuppressWarnings("javadoc")
38 | public class MinimalZipArchiveTest {
39 | private byte[] unitTestZipArchive;
40 | private File tempFile;
41 |
42 | @Before
43 | public void setup() throws Exception {
44 | unitTestZipArchive = UnitTestZipArchive.makeTestZip();
45 | tempFile = File.createTempFile("MinimalZipArchiveTest", "zip");
46 | tempFile.deleteOnExit();
47 | try {
48 | FileOutputStream out = new FileOutputStream(tempFile);
49 | out.write(unitTestZipArchive);
50 | out.flush();
51 | out.close();
52 | } catch (IOException e) {
53 | try {
54 | tempFile.delete();
55 | } catch (Exception ignored) {
56 | // Nothing
57 | }
58 | throw e;
59 | }
60 | }
61 |
62 | @After
63 | public void tearDown() {
64 | if (tempFile != null) {
65 | try {
66 | tempFile.delete();
67 | } catch (Exception ignored) {
68 | // Nothing
69 | }
70 | }
71 | }
72 |
73 | @Test
74 | public void testListEntries() throws IOException {
75 | // Ensure all entries are found, and that they are in file order.
76 | List parsedEntries = MinimalZipArchive.listEntries(tempFile);
77 | long lastSeenHeaderOffset = -1;
78 | for (int x = 0; x < UnitTestZipArchive.allEntriesInFileOrder.size(); x++) {
79 | UnitTestZipEntry expected = UnitTestZipArchive.allEntriesInFileOrder.get(x);
80 | MinimalZipEntry actual = parsedEntries.get(x);
81 | Assert.assertEquals(expected.path, actual.getFileName());
82 | Assert.assertEquals(expected.level == 0 ? 0 : 8, actual.getCompressionMethod());
83 | Assert.assertEquals(expected.getCompressedBinaryContent().length, actual.getCompressedSize());
84 | Assert.assertEquals(
85 | expected.getUncompressedBinaryContent().length, actual.getUncompressedSize());
86 | Assert.assertEquals(false, actual.getGeneralPurposeFlagBit11());
87 | CRC32 crc32 = new CRC32();
88 | crc32.update(expected.getUncompressedBinaryContent());
89 | Assert.assertEquals(crc32.getValue(), actual.getCrc32OfUncompressedData());
90 |
91 | // Offset verification is a little trickier
92 | // 1. Verify that the offsets are in ascending order and increasing.
93 | Assert.assertTrue(actual.getFileOffsetOfLocalEntry() > lastSeenHeaderOffset);
94 | lastSeenHeaderOffset = actual.getFileOffsetOfLocalEntry();
95 |
96 | // 2. Verify that the local signature header is at the calculated position
97 | byte[] expectedSignatureBlock = new byte[] {0x50, 0x4b, 0x03, 0x04};
98 | for (int index = 0; index < 4; index++) {
99 | byte actualByte = unitTestZipArchive[((int) actual.getFileOffsetOfLocalEntry()) + index];
100 | Assert.assertEquals(expectedSignatureBlock[index], actualByte);
101 | }
102 |
103 | // 3. Verify that the data is at the calculated position
104 | byte[] expectedContent = expected.getCompressedBinaryContent();
105 | int calculatedDataOffset = (int) actual.getFileOffsetOfCompressedData();
106 | for (int index = 0; index < expectedContent.length; index++) {
107 | Assert.assertEquals(
108 | expectedContent[index], unitTestZipArchive[calculatedDataOffset + index]);
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/PreDiffPlanTest.java:
--------------------------------------------------------------------------------
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 com.google.archivepatcher.shared.JreDeflateParameters;
18 | import com.google.archivepatcher.shared.TypedRange;
19 |
20 | import org.junit.Test;
21 | import org.junit.runner.RunWith;
22 | import org.junit.runners.JUnit4;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Arrays;
26 | import java.util.Collections;
27 | import java.util.List;
28 |
29 | /**
30 | * Tests for {@link PreDiffPlan}.
31 | */
32 | @RunWith(JUnit4.class)
33 | @SuppressWarnings("javadoc")
34 | public class PreDiffPlanTest {
35 | private static final List> SORTED_VOID_LIST =
36 | Collections.unmodifiableList(
37 | Arrays.asList(new TypedRange(0, 1, null), new TypedRange(1, 1, null)));
38 | private static final List> SORTED_DEFLATE_LIST =
39 | Collections.unmodifiableList(
40 | Arrays.asList(
41 | new TypedRange(0, 1, JreDeflateParameters.of(1, 0, true)),
42 | new TypedRange(1, 1, JreDeflateParameters.of(1, 0, true))));
43 |
44 | private List reverse(List list) {
45 | List reversed = new ArrayList(list);
46 | Collections.reverse(reversed);
47 | return reversed;
48 | }
49 |
50 | @Test
51 | public void testConstructor_OrderOK() {
52 | new PreDiffPlan(
53 | Collections.emptyList(), SORTED_VOID_LIST, SORTED_DEFLATE_LIST);
54 | }
55 |
56 | @Test(expected = IllegalArgumentException.class)
57 | public void testConstructor_OldFileUncompressionOrderNotOK() {
58 | new PreDiffPlan(
59 | Collections.emptyList(),
60 | reverse(SORTED_VOID_LIST),
61 | SORTED_DEFLATE_LIST,
62 | SORTED_DEFLATE_LIST);
63 | }
64 |
65 | @Test(expected = IllegalArgumentException.class)
66 | public void testConstructor_NewFileUncompressionOrderNotOK() {
67 | new PreDiffPlan(
68 | Collections.emptyList(),
69 | SORTED_VOID_LIST,
70 | reverse(SORTED_DEFLATE_LIST),
71 | SORTED_DEFLATE_LIST);
72 | }
73 |
74 | @Test(expected = IllegalArgumentException.class)
75 | public void testConstructor_NewFileRecompressionOrderNotOK() {
76 | new PreDiffPlan(
77 | Collections.emptyList(),
78 | SORTED_VOID_LIST,
79 | SORTED_DEFLATE_LIST,
80 | reverse(SORTED_DEFLATE_LIST));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/QualifiedRecommendationTest.java:
--------------------------------------------------------------------------------
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.util.Arrays;
18 | import java.util.Collections;
19 | import java.util.HashSet;
20 | import java.util.List;
21 | import java.util.Set;
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 | import org.junit.runner.RunWith;
25 | import org.junit.runners.JUnit4;
26 |
27 | /** Tests for {@link QualifiedRecommendation}. */
28 | @RunWith(JUnit4.class)
29 | @SuppressWarnings("javadoc")
30 | public class QualifiedRecommendationTest {
31 | private static final byte[] FILENAME1 = {'f', 'o', 'o'};
32 | private static final byte[] FILENAME2 = {'b', 'a', 'r'};
33 | private static final MinimalZipEntry ENTRY1 = new MinimalZipEntry(0, 1, 2, 3, FILENAME1, true, 0);
34 | private static final MinimalZipEntry ENTRY2 = new MinimalZipEntry(1, 2, 3, 4, FILENAME2, true, 0);
35 |
36 | private static final QualifiedRecommendation DEFAULT_QUALIFIED_RECOMMENDATION =
37 | new QualifiedRecommendation(
38 | ENTRY1,
39 | ENTRY2,
40 | Recommendation.UNCOMPRESS_BOTH,
41 | RecommendationReason.COMPRESSED_BYTES_CHANGED);
42 | private static final QualifiedRecommendation CLONED_DEFAULT_QUALIFIED_RECOMMENDATION =
43 | new QualifiedRecommendation(
44 | ENTRY1,
45 | ENTRY2,
46 | Recommendation.UNCOMPRESS_BOTH,
47 | RecommendationReason.COMPRESSED_BYTES_CHANGED);
48 | private static final QualifiedRecommendation ALTERED_ENTRY1 =
49 | new QualifiedRecommendation(
50 | ENTRY2,
51 | ENTRY2,
52 | Recommendation.UNCOMPRESS_BOTH,
53 | RecommendationReason.COMPRESSED_BYTES_CHANGED);
54 | private static final QualifiedRecommendation ALTERED_ENTRY2 =
55 | new QualifiedRecommendation(
56 | ENTRY1,
57 | ENTRY1,
58 | Recommendation.UNCOMPRESS_BOTH,
59 | RecommendationReason.COMPRESSED_BYTES_CHANGED);
60 | private static final QualifiedRecommendation ALTERED_RECOMMENDATION =
61 | new QualifiedRecommendation(
62 | ENTRY1,
63 | ENTRY2,
64 | Recommendation.UNCOMPRESS_NEITHER,
65 | RecommendationReason.COMPRESSED_BYTES_CHANGED);
66 | private static final QualifiedRecommendation ALTERED_REASON =
67 | new QualifiedRecommendation(
68 | ENTRY1, ENTRY2, Recommendation.UNCOMPRESS_BOTH, RecommendationReason.UNSUITABLE);
69 | private static final List ALL_MUTATIONS =
70 | Collections.unmodifiableList(
71 | Arrays.asList(ALTERED_ENTRY1, ALTERED_ENTRY2, ALTERED_RECOMMENDATION, ALTERED_REASON));
72 |
73 | @Test
74 | @SuppressWarnings("EqualsIncompatibleType") // For ErrorProne
75 | public void testEquals() {
76 | Assert.assertEquals(DEFAULT_QUALIFIED_RECOMMENDATION, DEFAULT_QUALIFIED_RECOMMENDATION);
77 | Assert.assertEquals(DEFAULT_QUALIFIED_RECOMMENDATION, CLONED_DEFAULT_QUALIFIED_RECOMMENDATION);
78 | Assert.assertNotSame(DEFAULT_QUALIFIED_RECOMMENDATION, CLONED_DEFAULT_QUALIFIED_RECOMMENDATION);
79 | for (QualifiedRecommendation mutation : ALL_MUTATIONS) {
80 | Assert.assertNotEquals(DEFAULT_QUALIFIED_RECOMMENDATION, mutation);
81 | }
82 | Assert.assertFalse(DEFAULT_QUALIFIED_RECOMMENDATION.equals(null));
83 | Assert.assertFalse(DEFAULT_QUALIFIED_RECOMMENDATION.equals("foo"));
84 | }
85 |
86 | @Test
87 | public void testHashCode() {
88 | Set hashSet = new HashSet<>();
89 | hashSet.add(DEFAULT_QUALIFIED_RECOMMENDATION);
90 | hashSet.add(CLONED_DEFAULT_QUALIFIED_RECOMMENDATION);
91 | Assert.assertEquals(1, hashSet.size());
92 | hashSet.addAll(ALL_MUTATIONS);
93 | Assert.assertEquals(1 + ALL_MUTATIONS.size(), hashSet.size());
94 | }
95 |
96 | @Test
97 | public void testGetters() {
98 | Assert.assertEquals(ENTRY1, DEFAULT_QUALIFIED_RECOMMENDATION.getOldEntry());
99 | Assert.assertEquals(ENTRY2, DEFAULT_QUALIFIED_RECOMMENDATION.getNewEntry());
100 | Assert.assertEquals(
101 | Recommendation.UNCOMPRESS_BOTH, DEFAULT_QUALIFIED_RECOMMENDATION.getRecommendation());
102 | Assert.assertEquals(
103 | RecommendationReason.COMPRESSED_BYTES_CHANGED,
104 | DEFAULT_QUALIFIED_RECOMMENDATION.getReason());
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/TempFileHolderTest.java:
--------------------------------------------------------------------------------
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 org.junit.Assert;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.junit.runners.JUnit4;
21 |
22 | import java.io.File;
23 | import java.io.IOException;
24 |
25 | /**
26 | * Tests for {@link TempFileHolder}.
27 | */
28 | @RunWith(JUnit4.class)
29 | @SuppressWarnings("javadoc")
30 | public class TempFileHolderTest {
31 | @Test
32 | public void testConstructAndClose() throws IOException {
33 | // Tests that a temp file can be created and that it is deleted upon close().
34 | File allocated = null;
35 | try(TempFileHolder holder = new TempFileHolder()) {
36 | Assert.assertNotNull(holder.file);
37 | Assert.assertTrue(holder.file.exists());
38 | allocated = holder.file;
39 | }
40 | Assert.assertFalse(allocated.exists());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/bsdiff/BsUtilTest.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import org.junit.Assert;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.junit.runners.JUnit4;
21 |
22 | import java.io.ByteArrayInputStream;
23 | import java.io.ByteArrayOutputStream;
24 | import java.io.IOException;
25 | import java.nio.charset.Charset;
26 |
27 | @RunWith(JUnit4.class)
28 | public class BsUtilTest {
29 | @Test
30 | public void writeFormattedLongTest() throws IOException {
31 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16);
32 | BsUtil.writeFormattedLong(0x12345678, outputStream);
33 | BsUtil.writeFormattedLong(0x0eadbeef, outputStream);
34 | byte[] actual = outputStream.toByteArray();
35 |
36 | byte[] expected = {
37 | (byte) 0x78,
38 | (byte) 0x56,
39 | (byte) 0x34,
40 | (byte) 0x12,
41 | (byte) 0,
42 | (byte) 0,
43 | (byte) 0,
44 | (byte) 0,
45 | (byte) 0xef,
46 | (byte) 0xbe,
47 | (byte) 0xad,
48 | (byte) 0x0e,
49 | (byte) 0,
50 | (byte) 0,
51 | (byte) 0,
52 | (byte) 0
53 | };
54 | Assert.assertArrayEquals(expected, actual);
55 | }
56 |
57 | @Test
58 | public void readFormattedLongTest() throws IOException {
59 | byte[] data = {
60 | (byte) 0x78,
61 | (byte) 0x56,
62 | (byte) 0x34,
63 | (byte) 0x12,
64 | (byte) 0,
65 | (byte) 0,
66 | (byte) 0,
67 | (byte) 0,
68 | (byte) 0xef,
69 | (byte) 0xbe,
70 | (byte) 0xad,
71 | (byte) 0x0e,
72 | (byte) 0,
73 | (byte) 0,
74 | (byte) 0,
75 | (byte) 0
76 | };
77 | ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
78 |
79 | Assert.assertEquals(0x12345678, BsUtil.readFormattedLong(inputStream));
80 | Assert.assertEquals(0x0eadbeef, BsUtil.readFormattedLong(inputStream));
81 | }
82 |
83 | private long writeThenReadFormattedLong(long value) throws IOException {
84 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8);
85 | BsUtil.writeFormattedLong(value, outputStream);
86 | byte[] outputBytes = outputStream.toByteArray();
87 | ByteArrayInputStream inputStream = new ByteArrayInputStream(outputBytes);
88 | return BsUtil.readFormattedLong(inputStream);
89 | }
90 |
91 | @Test
92 | public void writeThenReadFormattedLongTest() throws IOException {
93 | Assert.assertEquals(-1, writeThenReadFormattedLong(-1));
94 | Assert.assertEquals(0x7fffffff, writeThenReadFormattedLong(0x7fffffff));
95 | Assert.assertEquals(0, writeThenReadFormattedLong(0));
96 | Assert.assertEquals(Long.MAX_VALUE, writeThenReadFormattedLong(Long.MAX_VALUE));
97 | Assert.assertEquals(Long.MIN_VALUE, writeThenReadFormattedLong(Long.MIN_VALUE));
98 | }
99 |
100 | @Test
101 | public void lexicographicalCompareTest() throws IOException {
102 | String s1 = "this is a string";
103 | String s2 = "that was a string";
104 | byte[] s1b = s1.getBytes(Charset.forName("US-ASCII"));
105 | byte[] s2b = s2.getBytes(Charset.forName("US-ASCII"));
106 | RandomAccessObject s1ro = new RandomAccessObject.RandomAccessByteArrayObject(s1b);
107 | RandomAccessObject s2ro = new RandomAccessObject.RandomAccessByteArrayObject(s2b);
108 |
109 | int r = BsUtil.lexicographicalCompare(s1ro, 0, s1b.length, s2ro, 0, s2b.length);
110 | Assert.assertTrue(r > 0);
111 |
112 | r = BsUtil.lexicographicalCompare(s1ro, 5, s1b.length - 5, s2ro, 5, s2b.length - 5);
113 | Assert.assertTrue(r < 0);
114 |
115 | r = BsUtil.lexicographicalCompare(s1ro, 7, s1b.length - 7, s2ro, 8, s2b.length - 7);
116 | Assert.assertTrue(r < 0);
117 |
118 | r = BsUtil.lexicographicalCompare(s1ro, 7, s1b.length - 8, s2ro, 8, s2b.length - 8);
119 | Assert.assertTrue(r < 0);
120 |
121 | r = BsUtil.lexicographicalCompare(s1ro, 0, 2, s2ro, 0, 2);
122 | Assert.assertEquals(0, r);
123 |
124 | r = BsUtil.lexicographicalCompare(s1ro, 0, 1, s2ro, 0, 2);
125 | Assert.assertTrue(r < 0);
126 |
127 | r = BsUtil.lexicographicalCompare(s1ro, 0, 2, s2ro, 0, 1);
128 | Assert.assertTrue(r > 0);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/bsdiff/DivSuffixSorterTest.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import org.junit.Before;
18 | import org.junit.runner.RunWith;
19 | import org.junit.runners.JUnit4;
20 |
21 | @RunWith(JUnit4.class)
22 | public class DivSuffixSorterTest extends SuffixSorterTestBase {
23 |
24 | DivSuffixSorter divSuffixSorter;
25 |
26 | @Before
27 | public void setup() {
28 | divSuffixSorter =
29 | new DivSuffixSorter(new RandomAccessObjectFactory.RandomAccessByteArrayObjectFactory());
30 | }
31 |
32 | @Override
33 | public SuffixSorter getSuffixSorter() {
34 | return divSuffixSorter;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/generator/src/test/java/com/google/archivepatcher/generator/bsdiff/SuffixSorterTestBase.java:
--------------------------------------------------------------------------------
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.bsdiff;
16 |
17 | import static org.junit.Assert.fail;
18 |
19 | import java.io.IOException;
20 | import java.util.Random;
21 | import org.junit.Assert;
22 | import org.junit.Test;
23 |
24 | /**
25 | * Base class for suffix sorter tests with common tests for a suffix sorter algorithm.
26 | */
27 | public abstract class SuffixSorterTestBase {
28 |
29 | public abstract SuffixSorter getSuffixSorter();
30 |
31 | @Test
32 | public void suffixSortEmptyDataTest() throws Exception {
33 | checkSuffixSort( new int[] {0}, new byte[] {});
34 | }
35 |
36 | @Test
37 | public void suffixSortShortDataTest() throws Exception {
38 | checkSuffixSort(new int[] {1, 0}, new byte[] {23});
39 | checkSuffixSort(new int[] {2, 1, 0}, new byte[] {23, 20});
40 | checkSuffixSort(new int[] {2, 0, 1}, new byte[] {0, 127});
41 | checkSuffixSort(new int[] {2, 1, 0}, new byte[] {42, 42});
42 | }
43 |
44 | private void checkSuffixSort(int[] expectedSuffixArray, byte[] inputBytes) throws Exception {
45 | RandomAccessObject input = new RandomAccessObject.RandomAccessByteArrayObject(inputBytes);
46 | RandomAccessObject groupArray = getSuffixSorter().suffixSort(input);
47 |
48 | assertSorted(groupArray, input);
49 | Assert.assertArrayEquals(expectedSuffixArray, randomAccessObjectToIntArray(groupArray));
50 | }
51 |
52 | @Test
53 | public void suffixSortLongDataTest() throws Exception {
54 | RandomAccessObject groupArrayRO = getSuffixSorter().suffixSort(BsDiffTestData.LONG_DATA_99_RO);
55 |
56 | assertSorted(groupArrayRO, BsDiffTestData.LONG_DATA_99_RO);
57 |
58 | Assert.assertArrayEquals(
59 | BsDiffTestData.QUICK_SUFFIX_SORT_TEST_GA_CONTROL,
60 | randomAccessObjectToIntArray(groupArrayRO));
61 | }
62 |
63 | @Test
64 | public void suffixSortVeryLongDataTest() throws Exception {
65 | RandomAccessObject groupArray2RO =
66 | getSuffixSorter().suffixSort(BsDiffTestData.LONGER_DATA_349_RO);
67 |
68 | assertSorted(groupArray2RO, BsDiffTestData.LONGER_DATA_349_RO);
69 |
70 | Assert.assertArrayEquals(
71 | BsDiffTestData.QUICK_SUFFIX_SORT_TEST_IA_CONTROL,
72 | randomAccessObjectToIntArray(groupArray2RO));
73 | }
74 |
75 | @Test
76 | public void testRandom() throws Exception {
77 | Random rand = new Random(1123458);
78 | for (int i = 1; i <= 10; i++) {
79 | RandomAccessObject input = generateRandom(rand, i * 10000);
80 | RandomAccessObject suffixArray = getSuffixSorter().suffixSort(input);
81 |
82 | assertSorted(suffixArray, input);
83 | }
84 | }
85 |
86 | private static RandomAccessObject generateRandom(Random rand, int length) {
87 | byte[] bytes = new byte[length];
88 | rand.nextBytes(bytes);
89 | return new RandomAccessObject.RandomAccessByteArrayObject(bytes);
90 | }
91 |
92 | protected static RandomAccessObject intArrayToRandomAccessObject(final int[] array)
93 | throws Exception {
94 | RandomAccessObject ret =
95 | new RandomAccessObject.RandomAccessByteArrayObject(new byte[array.length * 4]);
96 | ret.seekToIntAligned(0);
97 |
98 | for (int element : array) {
99 | ret.writeInt(element);
100 | }
101 |
102 | return ret;
103 | }
104 |
105 | protected static boolean intArrayEqualsRandomAccessObject(
106 | int[] array, RandomAccessObject randomAccessObject) throws Exception {
107 | randomAccessObject.seekToIntAligned(0);
108 |
109 | for (int element : array) {
110 | if (element != randomAccessObject.readInt()) {
111 | return false;
112 | }
113 | }
114 |
115 | return true;
116 | }
117 |
118 | protected static int[] randomAccessObjectToIntArray(RandomAccessObject randomAccessObject)
119 | throws Exception {
120 | int[] ret = new int[(int) (randomAccessObject.length() / 4)];
121 | randomAccessObject.seekToIntAligned(0);
122 |
123 | for (int i = 0; i < ret.length; i++) {
124 | ret[i] = randomAccessObject.readInt();
125 | }
126 |
127 | return ret;
128 | }
129 |
130 | private static boolean checkSuffixLessThanOrEqual(
131 | RandomAccessObject input, int index1, int index2) throws Exception {
132 | while (true) {
133 | if (index1 == input.length()) {
134 | return true;
135 | }
136 | input.seek(index1);
137 | int unsignedByte1 = input.readUnsignedByte();
138 | input.seek(index2);
139 | int unsignedByte2 = input.readUnsignedByte();
140 | if (unsignedByte1 < unsignedByte2) {
141 | return true;
142 | }
143 | if (unsignedByte1 > unsignedByte2) {
144 | return false;
145 | }
146 | index1++;
147 | index2++;
148 | }
149 | }
150 |
151 | private static void assertSorted(RandomAccessObject suffixArray, RandomAccessObject input)
152 | throws Exception {
153 | for (int i = 0; i < input.length(); i++) {
154 | suffixArray.seekToIntAligned(i);
155 | int index1 = suffixArray.readInt();
156 | suffixArray.seekToIntAligned(i+1);
157 | int index2 = suffixArray.readInt();
158 | if (!checkSuffixLessThanOrEqual(input, index1, index2)) {
159 | fail();
160 | }
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/generator/src/test/resources/com/google/archivepatcher/generator/bsdiff/testdata/BsDiffInternalTestNew.txt:
--------------------------------------------------------------------------------
1 | TL?I"a)tAARPRLH)ZNuYRsdf8yu032D#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bKo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk}+A2lO;D#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk} \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/integrationtest/build.gradle:
--------------------------------------------------------------------------------
1 | // intergationtest module
2 | apply plugin: 'java'
3 |
4 | dependencies {
5 | compile project(':shared')
6 | compile project(':applier')
7 | compile project(':generator')
8 |
9 | testCompile 'junit:junit:4.12'
10 | testCompile project(':sharedtest')
11 | }
12 | // EOF
13 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/google/archivepatcher/sample/SamplePatchApplier.java:
--------------------------------------------------------------------------------
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.sample;
16 |
17 | import com.google.archivepatcher.applier.FileByFileV1DeltaApplier;
18 | import com.google.archivepatcher.shared.DefaultDeflateCompatibilityWindow;
19 | import java.io.File;
20 | import java.io.FileInputStream;
21 | import java.io.FileOutputStream;
22 | import java.util.zip.Inflater;
23 | import java.util.zip.InflaterInputStream;
24 |
25 | /** Apply a patch; args are old file path, patch file path, and new file path. */
26 | public class SamplePatchApplier {
27 | public static void main(String... args) throws Exception {
28 | if (!new DefaultDeflateCompatibilityWindow().isCompatible()) {
29 | System.err.println("zlib not compatible on this system");
30 | System.exit(-1);
31 | }
32 | File oldFile = new File(args[0]); // must be a zip archive
33 | Inflater uncompressor = new Inflater(true);
34 | try (FileInputStream compressedPatchIn = new FileInputStream(args[1]);
35 | InflaterInputStream patchIn =
36 | new InflaterInputStream(compressedPatchIn, uncompressor, 32768);
37 | FileOutputStream newFileOut = new FileOutputStream(args[2])) {
38 | new FileByFileV1DeltaApplier().applyDelta(oldFile, patchIn, newFileOut);
39 | } finally {
40 | uncompressor.end();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/google/archivepatcher/sample/SamplePatchGenerator.java:
--------------------------------------------------------------------------------
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.sample;
16 |
17 | import com.google.archivepatcher.generator.FileByFileV1DeltaGenerator;
18 | import com.google.archivepatcher.shared.DefaultDeflateCompatibilityWindow;
19 | import java.io.File;
20 | import java.io.FileOutputStream;
21 | import java.util.zip.Deflater;
22 | import java.util.zip.DeflaterOutputStream;
23 |
24 | /** Generate a patch; args are old file path, new file path, and patch file path. */
25 | public class SamplePatchGenerator {
26 | public static void main(String... args) throws Exception {
27 | if (!new DefaultDeflateCompatibilityWindow().isCompatible()) {
28 | System.err.println("zlib not compatible on this system");
29 | System.exit(-1);
30 | }
31 | File oldFile = new File(args[0]); // must be a zip archive
32 | File newFile = new File(args[1]); // must be a zip archive
33 | Deflater compressor = new Deflater(9, true); // to compress the patch
34 | try (FileOutputStream patchOut = new FileOutputStream(args[2]);
35 | DeflaterOutputStream compressedPatchOut =
36 | new DeflaterOutputStream(patchOut, compressor, 32768)) {
37 | new FileByFileV1DeltaGenerator().generateDelta(oldFile, newFile, compressedPatchOut);
38 | compressedPatchOut.finish();
39 | compressedPatchOut.flush();
40 | } finally {
41 | compressor.end();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sharedtest'
2 | include ':shared'
3 | include ':applier'
4 | include ':generator'
5 | include ':explainer'
6 | include ':tools'
7 | include ':integrationtest'
8 |
--------------------------------------------------------------------------------
/shared/build.gradle:
--------------------------------------------------------------------------------
1 | // shared module
2 |
3 | apply plugin: 'java'
4 |
5 | dependencies {
6 | testCompile 'junit:junit:4.12'
7 | testCompile project(':sharedtest')
8 | }
9 | // EOF
10 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/ByteArrayInputStreamFactory.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.ByteArrayInputStream;
18 | import java.io.IOException;
19 |
20 | /**
21 | * A {@link MultiViewInputStreamFactory} which creates {@link ByteArrayInputStream}s based on the
22 | * given {@code byte[]} in {@link #ByteArrayInputStreamFactory(byte[])}.
23 | */
24 | public class ByteArrayInputStreamFactory implements MultiViewInputStreamFactory {
25 |
26 | private final byte[] bytes;
27 |
28 | public ByteArrayInputStreamFactory(byte[] bytes) {
29 | this.bytes = bytes;
30 | }
31 |
32 | @Override
33 | public ByteArrayInputStream newStream() throws IOException {
34 | return new ByteArrayInputStream(bytes);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/Compressor.java:
--------------------------------------------------------------------------------
1 | // Copyright 2015 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.shared;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | /**
22 | * An interface for implementing a streaming compressor. A compressor may be used to compress
23 | * arbitrary binary data, but is always capable of doing so in a streaming manner.
24 | */
25 | public interface Compressor {
26 | /**
27 | * Compresses data, writing the compressed data into compressedOut.
28 | *
29 | * @param uncompressedIn the uncompressed data
30 | * @param compressedOut the compressed data
31 | * @throws IOException if something goes awry while reading or writing
32 | */
33 | public void compress(InputStream uncompressedIn, OutputStream compressedOut) throws IOException;
34 | }
35 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/CountingOutputStream.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.FilterOutputStream;
18 | import java.io.IOException;
19 | import java.io.OutputStream;
20 |
21 | /**
22 | * Trivial output stream that counts the bytes written to it.
23 | */
24 | public class CountingOutputStream extends FilterOutputStream {
25 | /**
26 | * Number of bytes written so far.
27 | */
28 | private long bytesWritten = 0;
29 |
30 | /**
31 | * Create a new counting output stream.
32 | * @param out the output stream to wrap
33 | */
34 | public CountingOutputStream(OutputStream out) {
35 | super(out);
36 | }
37 |
38 | /**
39 | * Returns the number of bytes written to this stream so far.
40 | * @return as described
41 | */
42 | public long getNumBytesWritten() {
43 | return bytesWritten;
44 | }
45 |
46 | @Override
47 | public void write(int b) throws IOException {
48 | bytesWritten++;
49 | out.write(b);
50 | }
51 |
52 | @Override
53 | public void write(byte[] b) throws IOException {
54 | bytesWritten += b.length;
55 | out.write(b);
56 | }
57 |
58 | @Override
59 | public void write(byte[] b, int off, int len) throws IOException {
60 | bytesWritten += len;
61 | out.write(b, off, len);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/MultiViewInputStreamFactory.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 |
20 | /**
21 | * A factory that produces multiple independent but identical byte streams exposed via the {@link
22 | * InputStream} class.
23 | */
24 | public interface MultiViewInputStreamFactory {
25 | /**
26 | * Create and return a new {@link InputStream}. The returned stream is guaranteed to independently
27 | * produce the same byte sequence as any other stream obtained via a call to this method on the
28 | * same instance of this object.
29 | *
30 | * @return the stream
31 | * @throws IOException if something goes wrong
32 | */
33 | public InputStream newStream() throws IOException;
34 | }
35 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/PartiallyUncompressingPipe.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.Closeable;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.OutputStream;
21 |
22 | /**
23 | * A pipe that moves data from an {@link InputStream} to an {@link OutputStream}, optionally
24 | * uncompressing the input data on-the-fly.
25 | */
26 | public class PartiallyUncompressingPipe implements Closeable {
27 | /**
28 | * The uncompressor used to uncompress compressed input streams.
29 | */
30 | private final DeflateUncompressor uncompressor;
31 |
32 | /**
33 | * The output stream to write to.
34 | */
35 | private final CountingOutputStream out;
36 |
37 | /**
38 | * A buffer used when copying bytes.
39 | */
40 | private final byte[] copyBuffer;
41 |
42 | /**
43 | * Modes available for {@link PartiallyUncompressingPipe#pipe(InputStream, Mode)}.
44 | */
45 | public static enum Mode {
46 | /**
47 | * Copy bytes form the {@link InputStream} to the {@link OutputStream} without modification.
48 | */
49 | COPY,
50 |
51 | /**
52 | * Treat the {@link InputStream} as a deflate stream with nowrap=false, uncompress the bytes
53 | * on-the-fly and write the uncompressed data to the {@link OutputStream}.
54 | */
55 | UNCOMPRESS_WRAPPED,
56 |
57 | /**
58 | * Treat the {@link InputStream} as a deflate stream with nowrap=true, uncompress the bytes
59 | * on-the-fly and write the uncompressed data to the {@link OutputStream}.
60 | */
61 | UNCOMPRESS_NOWRAP,
62 | }
63 |
64 | /**
65 | * Constructs a new stream.
66 | * @param out the stream, to write to
67 | * @param copyBufferSize the size of the buffer to use when copying instead of uncompressing
68 | */
69 | public PartiallyUncompressingPipe(OutputStream out, int copyBufferSize) {
70 | this.out = new CountingOutputStream(out);
71 | uncompressor = new DeflateUncompressor();
72 | uncompressor.setCaching(true);
73 | copyBuffer = new byte[copyBufferSize];
74 | }
75 |
76 | /**
77 | * Pipes the entire contents of the specified {@link InputStream} to the configured
78 | * {@link OutputStream}, optionally uncompressing on-the-fly.
79 | * @param in the stream to read from
80 | * @param mode the mode to use for reading and writing
81 | * @return the number of bytes written to the output stream
82 | * @throws IOException if anything goes wrong
83 | */
84 | public long pipe(InputStream in, Mode mode) throws IOException {
85 | long bytesWrittenBefore = out.getNumBytesWritten();
86 | if (mode == Mode.COPY) {
87 | int numRead = 0;
88 | while ((numRead = in.read(copyBuffer)) >= 0) {
89 | out.write(copyBuffer, 0, numRead);
90 | }
91 | } else {
92 | uncompressor.setNowrap(mode == Mode.UNCOMPRESS_NOWRAP);
93 | uncompressor.uncompress(in, out);
94 | }
95 | out.flush();
96 | return out.getNumBytesWritten() - bytesWrittenBefore;
97 | }
98 |
99 | /**
100 | * Returns the number of bytes written to the stream so far.
101 | * @return as described
102 | */
103 | public long getNumBytesWritten() {
104 | return out.getNumBytesWritten();
105 | }
106 |
107 | @Override
108 | public void close() throws IOException {
109 | uncompressor.release();
110 | out.close();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/PatchConstants.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | /**
18 | * Constants used in reading and writing patches.
19 | */
20 | public class PatchConstants {
21 | /**
22 | * The identifier that begins all patches of this type.
23 | */
24 | public static final String IDENTIFIER = "GFbFv1_0"; // Google File-by-File v1.0
25 |
26 | /**
27 | * All available compatibility windows. The {@link #patchValue} field specifies the value for
28 | * each constant as represented in a patch file.
29 | */
30 | public static enum CompatibilityWindowId {
31 | /**
32 | * The {@link com.google.archivepatcher.shared.DefaultDeflateCompatibilityWindow}.
33 | */
34 | DEFAULT_DEFLATE((byte) 0);
35 |
36 | /**
37 | * The representation of this enumerated constant in patch files.
38 | */
39 | public final byte patchValue;
40 |
41 | /**
42 | * Construct a new enumerated constant with the specified value in patch files.
43 | */
44 | private CompatibilityWindowId(byte patchValue) {
45 | this.patchValue = patchValue;
46 | }
47 |
48 | /**
49 | * Parse a patch value and return the corresponding enumerated constant.
50 | * @param patchValue the patch value to parse
51 | * @return the corresponding enumerated constant, null if unmatched
52 | */
53 | public static CompatibilityWindowId fromPatchValue(byte patchValue) {
54 | switch (patchValue) {
55 | case 0:
56 | return DEFAULT_DEFLATE;
57 | default:
58 | return null;
59 | }
60 | }
61 | }
62 |
63 | /**
64 | * All available delta formats. The {@link #patchValue} field specifies the value for each
65 | * constant as represented in a patch file.
66 | */
67 | public static enum DeltaFormat {
68 | /**
69 | * The bsdiff delta format.
70 | */
71 | BSDIFF((byte) 0);
72 |
73 | /**
74 | * The representation of this enumerated constant in patch files.
75 | */
76 | public final byte patchValue;
77 |
78 | /**
79 | * Construct a new enumerated constant with the specified value in patch files.
80 | */
81 | private DeltaFormat(byte patchValue) {
82 | this.patchValue = patchValue;
83 | }
84 |
85 | /**
86 | * Parse a patch value and return the corresponding enumerated constant.
87 | * @param patchValue the patch value to parse
88 | * @return the corresponding enumerated constant, null if unmatched
89 | */
90 | public static DeltaFormat fromPatchValue(byte patchValue) {
91 | switch (patchValue) {
92 | case 0:
93 | return BSDIFF;
94 | default:
95 | return null;
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/RandomAccessFileInputStreamFactory.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.File;
18 | import java.io.IOException;
19 |
20 | /**
21 | * An implementation of {@link MultiViewInputStreamFactory} that produces instances of {@link
22 | * RandomAccessFileInputStream}.
23 | */
24 | public class RandomAccessFileInputStreamFactory implements MultiViewInputStreamFactory {
25 |
26 | /**
27 | * Argument for {@link RandomAccessFileInputStream#RandomAccessFileInputStream(File, long, long)}.
28 | */
29 | private final File file;
30 |
31 | /**
32 | * Argument for {@link RandomAccessFileInputStream#RandomAccessFileInputStream(File, long, long)}.
33 | */
34 | private final long rangeOffset;
35 |
36 | /**
37 | * Argument for {@link RandomAccessFileInputStream#RandomAccessFileInputStream(File, long, long)}.
38 | */
39 | private final long rangeLength;
40 |
41 | /**
42 | * Constructs a new factory that will create instances of {@link RandomAccessFileInputStream} with
43 | * the specified parameters.
44 | * @param file the file to use in {@link #newStream()}
45 | * @param rangeOffset the range offset to use in {@link #newStream()}
46 | * @param rangeLength the range length to use in {@link #newStream()}
47 | */
48 | public RandomAccessFileInputStreamFactory(File file, long rangeOffset, long rangeLength) {
49 | this.file = file;
50 | this.rangeOffset = rangeOffset;
51 | this.rangeLength = rangeLength;
52 | }
53 |
54 | @Override
55 | public RandomAccessFileInputStream newStream() throws IOException {
56 | return new RandomAccessFileInputStream(file, rangeOffset, rangeLength);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/RandomAccessFileOutputStream.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.io.OutputStream;
20 | import java.io.RandomAccessFile;
21 |
22 | /**
23 | * An {@link OutputStream} backed by a file that will be written serially. Allows pre-allocating
24 | * the space for a stream as a file and then writing to that file as a stream. Call {@link #flush()}
25 | * to force the data to be written to the backing storage.
26 | */
27 | public class RandomAccessFileOutputStream extends OutputStream {
28 | /**
29 | * The backing {@link RandomAccessFile}.
30 | */
31 | private final RandomAccessFile raf;
32 |
33 | /**
34 | * Constructs a new instance that will immediately open the specified file for writing and set
35 | * the length to the specified value.
36 | * @param outputFile the file to wrap
37 | * @param expectedSize if greater than or equal to zero, the size to set the file to immediately;
38 | * otherwise, the file size is not set
39 | * @throws IOException if unable to open the file for writing or set the size
40 | */
41 | public RandomAccessFileOutputStream(File outputFile, long expectedSize) throws IOException {
42 | this.raf = getRandomAccessFile(outputFile);
43 | if (expectedSize >= 0) {
44 | raf.setLength(expectedSize);
45 | if (raf.length() != expectedSize) {
46 | throw new IOException("Unable to set the file size");
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * Given a {@link File}, get a writeable {@link RandomAccessFile} reference for it.
53 | * @param file the file
54 | * @return as described
55 | * @throws IOException if unable to open the file
56 | */
57 | protected RandomAccessFile getRandomAccessFile(File file) throws IOException {
58 | return new RandomAccessFile(file, "rw");
59 | }
60 |
61 | @Override
62 | public void write(int b) throws IOException {
63 | raf.write(b);
64 | }
65 |
66 | @Override
67 | public void write(byte[] b) throws IOException {
68 | write(b, 0, b.length);
69 | }
70 |
71 | @Override
72 | public void write(byte[] b, int off, int len) throws IOException {
73 | raf.write(b, off, len);
74 | }
75 |
76 | @Override
77 | public void flush() throws IOException {
78 | raf.getChannel().force(true);
79 | }
80 |
81 | @Override
82 | public void close() throws IOException {
83 | flush();
84 | raf.close();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/TypedRange.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | /**
18 | * A range, annotated with metadata, that is represented as an offset and a length. Comparison is
19 | * performed based on the natural ordering of the offset field.
20 | * @param the type of the metadata
21 | */
22 | public class TypedRange implements Comparable> {
23 | /**
24 | * The offset at which the range starts.
25 | */
26 | private final long offset;
27 |
28 | /**
29 | * The length of the range.
30 | */
31 | private final long length;
32 |
33 | /**
34 | * Optional metadata associated with this range.
35 | */
36 | private final T metadata;
37 |
38 | /**
39 | * Constructs a new range with the specified parameters.
40 | * @param offset the offset at which the range starts
41 | * @param length the length of the range
42 | * @param metadata optional metadata associated with this range
43 | */
44 | public TypedRange(long offset, long length, T metadata) {
45 | this.offset = offset;
46 | this.length = length;
47 | this.metadata = metadata;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "offset " + offset + ", length " + length + ", metadata " + metadata;
53 | }
54 |
55 | /**
56 | * Returns the offset at which the range starts.
57 | * @return as described
58 | */
59 | public long getOffset() {
60 | return offset;
61 | }
62 |
63 | /**
64 | * Returns the length of the range.
65 | * @return as described
66 | */
67 | public long getLength() {
68 | return length;
69 | }
70 |
71 | /**
72 | * Returns the metadata associated with the range, or null if no metadata has been set.
73 | * @return as described
74 | */
75 | public T getMetadata() {
76 | return metadata;
77 | }
78 |
79 | @Override
80 | public int hashCode() {
81 | final int prime = 31;
82 | int result = 1;
83 | result = prime * result + (int) (length ^ (length >>> 32));
84 | result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
85 | result = prime * result + (int) (offset ^ (offset >>> 32));
86 | return result;
87 | }
88 |
89 | @Override
90 | public boolean equals(Object obj) {
91 | if (this == obj) return true;
92 | if (obj == null) return false;
93 | if (getClass() != obj.getClass()) return false;
94 | TypedRange> other = (TypedRange>) obj;
95 | if (length != other.length) return false;
96 | if (metadata == null) {
97 | if (other.metadata != null) return false;
98 | } else if (!metadata.equals(other.metadata)) return false;
99 | if (offset != other.offset) return false;
100 | return true;
101 | }
102 |
103 | @Override
104 | public int compareTo(TypedRange other) {
105 | if (getOffset() < other.getOffset()) {
106 | return -1;
107 | } else if (getOffset() > other.getOffset()) {
108 | return 1;
109 | }
110 | return 0;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/google/archivepatcher/shared/Uncompressor.java:
--------------------------------------------------------------------------------
1 | // Copyright 2015 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.shared;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | /**
22 | * An interface for implementing a streaming uncompressor. An uncompressor may be used to uncompress
23 | * data that was previously compressed by the corresponding {@link Compressor} implementation, and
24 | * always operates in a streaming manner.
25 | */
26 | public interface Uncompressor {
27 | /**
28 | * Uncompresses data that was previously processed by the corresponding {@link Compressor}
29 | * implementation, writing the uncompressed data into uncompressedOut.
30 | *
31 | * @param compressedIn the compressed data
32 | * @param uncompressedOut the uncompressed data
33 | * @throws IOException if something goes awry while reading or writing
34 | */
35 | public void uncompress(InputStream compressedIn, OutputStream uncompressedOut) throws IOException;
36 | }
37 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/CountingOutputStreamTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import org.junit.Assert;
18 | import org.junit.Before;
19 | import org.junit.Test;
20 | import org.junit.runner.RunWith;
21 | import org.junit.runners.JUnit4;
22 |
23 | import java.io.ByteArrayOutputStream;
24 | import java.io.IOException;
25 | import java.io.OutputStream;
26 |
27 | /**
28 | * Tests for {@link CountingOutputStream}.
29 | */
30 | @RunWith(JUnit4.class)
31 | @SuppressWarnings("javadoc")
32 | public class CountingOutputStreamTest {
33 | private ByteArrayOutputStream outBuffer;
34 | private CountingOutputStream stream;
35 |
36 | /**
37 | * Helper class that discards all output.
38 | */
39 | private static class NullStream extends OutputStream {
40 | @Override
41 | public void write(int b) throws IOException {
42 | // Do nothing
43 | }
44 |
45 | @Override
46 | public void write(byte[] b) throws IOException {
47 | // Do nothing
48 | }
49 |
50 | @Override
51 | public void write(byte[] b, int off, int len) throws IOException {
52 | // Do nothing
53 | }
54 | }
55 |
56 | @Before
57 | public void setup() {
58 | outBuffer = new ByteArrayOutputStream();
59 | stream = new CountingOutputStream(outBuffer);
60 | }
61 |
62 | @Test
63 | public void testGetNumBytesWritten_Zero() {
64 | Assert.assertEquals(0, stream.getNumBytesWritten());
65 | }
66 |
67 | @Test
68 | public void testGetNumBytesWritten_FewBytes() throws IOException {
69 | stream.write(1);
70 | Assert.assertEquals(1, stream.getNumBytesWritten());
71 | stream.write(new byte[] {2, 3, 4});
72 | Assert.assertEquals(4, stream.getNumBytesWritten());
73 | stream.write(new byte[] {4, 5, 6, 7, 8}, 1, 3); // Write only {5, 6, 7}
74 | Assert.assertEquals(7, stream.getNumBytesWritten());
75 | byte[] expected = new byte[] {1, 2, 3, 4, 5, 6, 7};
76 | Assert.assertArrayEquals(expected, outBuffer.toByteArray());
77 | }
78 |
79 | @Test
80 | public void testGetNumBytesWritten_PastIntegerMaxValue() throws IOException {
81 | // Make a 1MB buffer. Iterating over this 2048 times will take the test to the 2GB limit of
82 | // Integer.maxValue. Use a NullStream to avoid excessive memory usage and make the test fast.
83 | stream = new CountingOutputStream(new NullStream());
84 | byte[] buffer = new byte[1024 * 1024];
85 | for (int x = 0; x < 2048; x++) {
86 | stream.write(buffer);
87 | }
88 | long expected = 2048L * 1024L * 1024L; // == 2GB, Integer.MAX_VALUE + 1
89 | Assert.assertTrue(expected > Integer.MAX_VALUE);
90 | Assert.assertEquals(expected, stream.getNumBytesWritten());
91 | // Push it well past 4GB
92 | for (int x = 0; x < 78053; x++) {
93 | stream.write(buffer);
94 | expected += buffer.length;
95 | Assert.assertEquals(expected, stream.getNumBytesWritten());
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/DefaultDeflateCompatibilityWindowTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.util.HashMap;
18 | import java.util.Map;
19 | import org.junit.Assert;
20 | import org.junit.Before;
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 | import org.junit.runners.JUnit4;
24 |
25 | /**
26 | * Tests for {@link DefaultDeflateCompatibilityWindow}.
27 | */
28 | @RunWith(JUnit4.class)
29 | @SuppressWarnings("javadoc")
30 | public class DefaultDeflateCompatibilityWindowTest {
31 |
32 | private DefaultDeflateCompatibilityWindow window = null;
33 |
34 | private final JreDeflateParameters brokenParameters = JreDeflateParameters.of(1, 0, true);
35 |
36 | /**
37 | * Trivial subclass for testing, always fails compatibility checks.
38 | */
39 | private class BrokenCompatibilityWindow extends DefaultDeflateCompatibilityWindow {
40 | @Override
41 | public Map getBaselineValues() {
42 | // Superclass's version is immutable, so make a copy.
43 | Map result =
44 | new HashMap(super.getBaselineValues());
45 | result.put(brokenParameters, "foo");
46 | return result;
47 | }
48 | }
49 |
50 | private class InfallibleCompatibilityWindow extends DefaultDeflateCompatibilityWindow {
51 | @Override
52 | public Map getBaselineValues() {
53 | // Using the system values for the baseline means the baseline will always match :)
54 | return getSystemValues();
55 | }
56 | }
57 |
58 | @Before
59 | public void setUp() {
60 | window = new DefaultDeflateCompatibilityWindow();
61 | }
62 |
63 | @Test
64 | public void testGetBaselineValues() {
65 | // Basic sanity test: ensure it doesn't crash and isn't null, and contains all the values that
66 | // we care about.
67 | ensureHasAllKeys(window.getBaselineValues());
68 | }
69 |
70 | @Test
71 | public void testGetSystemValues() {
72 | // Basic sanity test: ensure it doesn't crash and isn't null, and contains all the values that
73 | // we care about.
74 | ensureHasAllKeys(window.getSystemValues());
75 | }
76 |
77 | private void ensureHasAllKeys(Map mappings) {
78 | for (int level = 1; level <= 9; level++) {
79 | for (int strategy = 0; strategy <= 1; strategy++) {
80 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(level, strategy, true)));
81 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(level, strategy, false)));
82 | }
83 | }
84 | // Manually scan for presence of the strategy-2 values, only set for compression level 1....
85 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(1, 2, true)));
86 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(1, 2, false)));
87 | Assert.assertEquals(38, mappings.size());
88 | }
89 |
90 | @Test
91 | public void testGetCorpus() {
92 | // Basic sanity test: ensure it's a non-null, non-empty return.
93 | byte[] corpus1 = window.getCorpus();
94 | Assert.assertNotNull(corpus1);
95 | Assert.assertTrue(corpus1.length > 0);
96 | // Basic sanity test: ensure the corpus is distinct each time the method is called (i.e., the
97 | // mutable object returned is independent of the actual corpus).
98 | byte[] corpus2 = window.getCorpus();
99 | Assert.assertArrayEquals(corpus1, corpus2);
100 | Assert.assertNotSame(corpus1, corpus2);
101 | }
102 |
103 | @Test
104 | public void testIsCompatible() {
105 | // First do a coverage-only call, as it's not safe to assume compatibility in the unit test.
106 | window.isCompatible();
107 | // Now do a call that is guaranteed to fail.
108 | BrokenCompatibilityWindow broken = new BrokenCompatibilityWindow();
109 | Assert.assertFalse(broken.isCompatible());
110 | // Now do a call that is guaranteed to succeed.
111 | InfallibleCompatibilityWindow infallible = new InfallibleCompatibilityWindow();
112 | Assert.assertTrue(infallible.isCompatible());
113 | }
114 |
115 | @Test
116 | public void testGetIncompatibleValues() {
117 | // First do a coverage-only call, as it's not safe to assume compatibility in the unit test.
118 | Assert.assertNotNull(window.getIncompatibleValues());
119 | // Now do a call that is guaranteed to produce failure data.
120 | BrokenCompatibilityWindow brokenWindow = new BrokenCompatibilityWindow();
121 | Map incompatible = brokenWindow.getIncompatibleValues();
122 | Assert.assertTrue(incompatible.containsKey(brokenParameters));
123 | // Now do a call that is guaranteed to produce no failure data.
124 | InfallibleCompatibilityWindow infallible = new InfallibleCompatibilityWindow();
125 | Assert.assertTrue(infallible.getIncompatibleValues().isEmpty());
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/JreDeflateParametersTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import org.junit.Assert;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.junit.runners.JUnit4;
21 |
22 | /**
23 | * Tests for {@link DefaultDeflateCompatibilityWindow}.
24 | */
25 | @RunWith(JUnit4.class)
26 | @SuppressWarnings("javadoc")
27 | public class JreDeflateParametersTest {
28 |
29 | @Test
30 | public void testOf_AllValidValues() {
31 | for (int level = 1; level <= 9; level++) {
32 | for (int strategy = 0; strategy <= 2; strategy++) {
33 | for (boolean nowrap : new boolean[] {true, false}) {
34 | JreDeflateParameters.of(level, strategy, nowrap);
35 | }
36 | }
37 | }
38 | }
39 |
40 | private void assertIllegalArgumentException(int level, int strategy, boolean nowrap) {
41 | try {
42 | JreDeflateParameters.of(level, strategy, nowrap);
43 | Assert.fail("Invalid configuration allowed");
44 | } catch (IllegalArgumentException expected) {
45 | // Pass
46 | }
47 | }
48 |
49 | @Test
50 | public void testOf_InvalidValues() {
51 | // All of these should fail.
52 | assertIllegalArgumentException(0, 0, true); // Bad compression level (store)
53 | assertIllegalArgumentException(-1, 0, true); // Bad compression level (insane value < 0)
54 | assertIllegalArgumentException(10, 0, true); // Bad compression level (insane value > 9)
55 | assertIllegalArgumentException(1, -1, true); // Bad strategy (insane value < 0)
56 | assertIllegalArgumentException(1, 3, true); // Bad strategy (valid in zlib, unsupported in JRE)
57 | }
58 |
59 | @Test
60 | public void testToString() {
61 | // Ensure that toString() doesn't crash and produces a non-empty string.
62 | Assert.assertTrue(JreDeflateParameters.of(1, 0, true).toString().length() > 0);
63 | }
64 |
65 | @Test
66 | public void testParseString() {
67 | for (int level = 1; level <= 9; level++) {
68 | for (int strategy = 0; strategy <= 2; strategy++) {
69 | for (boolean nowrap : new boolean[] {true, false}) {
70 | JreDeflateParameters params = JreDeflateParameters.of(level, strategy, nowrap);
71 | String asString = params.toString();
72 | JreDeflateParameters fromString = JreDeflateParameters.parseString(asString);
73 | Assert.assertEquals(params, fromString);
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/PartiallyUncompressingPipeTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import com.google.archivepatcher.shared.PartiallyUncompressingPipe.Mode;
18 |
19 | import org.junit.Assert;
20 | import org.junit.Before;
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 | import org.junit.runners.JUnit4;
24 |
25 | import java.io.ByteArrayInputStream;
26 | import java.io.ByteArrayOutputStream;
27 | import java.io.IOException;
28 |
29 | /**
30 | * Tests for {@link PartiallyUncompressingPipe}.
31 | */
32 | @RunWith(JUnit4.class)
33 | @SuppressWarnings("javadoc")
34 | public class PartiallyUncompressingPipeTest {
35 | private ByteArrayOutputStream outBuffer;
36 | private PartiallyUncompressingPipe stream;
37 |
38 | @Before
39 | public void setup() {
40 | outBuffer = new ByteArrayOutputStream();
41 | stream = new PartiallyUncompressingPipe(outBuffer, 32768);
42 | }
43 |
44 | @Test
45 | public void testWriteAll_Uncompressed() throws IOException {
46 | byte[] expectedBytes = new byte[] {1, 2, 3, 4, 5};
47 | stream.pipe(new ByteArrayInputStream(expectedBytes), Mode.COPY);
48 | Assert.assertArrayEquals(expectedBytes, outBuffer.toByteArray());
49 | }
50 |
51 | @Test
52 | public void testWriteAll_Compressed_NoWrapTrue() throws IOException {
53 | UnitTestZipEntry entry = UnitTestZipArchive.makeUnitTestZipEntry("/foo", 7, "frobozz", null);
54 | stream.pipe(
55 | new ByteArrayInputStream(entry.getCompressedBinaryContent()), Mode.UNCOMPRESS_NOWRAP);
56 | Assert.assertArrayEquals(entry.getUncompressedBinaryContent(), outBuffer.toByteArray());
57 | }
58 |
59 | @Test
60 | public void testWriteAll_Compressed_NoWrapFalse() throws IOException {
61 | UnitTestZipEntry entry = UnitTestZipArchive.makeUnitTestZipEntry("/foo", 6, "frobozz", null);
62 |
63 | // Make a compressor with nowrap set to *false* (unusual) and pump the uncompressed entry
64 | // content through it.
65 | DeflateCompressor compressor = new DeflateCompressor();
66 | compressor.setNowrap(false);
67 | ByteArrayOutputStream compressBuffer = new ByteArrayOutputStream();
68 | compressor.compress(
69 | new ByteArrayInputStream(entry.getUncompressedBinaryContent()), compressBuffer);
70 |
71 | // Now use the compressed data as input to the PartiallyUncompressingPipe.
72 | stream.pipe(new ByteArrayInputStream(compressBuffer.toByteArray()), Mode.UNCOMPRESS_WRAPPED);
73 | Assert.assertArrayEquals(entry.getUncompressedBinaryContent(), outBuffer.toByteArray());
74 | }
75 |
76 | @Test
77 | public void testWriteAll_Multiple() throws IOException {
78 | // A series of uncompressed, compressed, uncompressed, compressed, uncompressed bytes.
79 | UnitTestZipEntry entryA =
80 | UnitTestZipArchive.makeUnitTestZipEntry("/bar", 3, "dragon lance", null);
81 | UnitTestZipEntry entryB =
82 | UnitTestZipArchive.makeUnitTestZipEntry("/baz", 8, "kender & hoopak", null);
83 | ByteArrayOutputStream expected = new ByteArrayOutputStream();
84 |
85 | // Write everything
86 | byte[] expectedBytes1 = new byte[] {1, 2, 3, 4, 5};
87 | expected.write(expectedBytes1);
88 | stream.pipe(new ByteArrayInputStream(expectedBytes1), Mode.COPY);
89 |
90 | stream.pipe(
91 | new ByteArrayInputStream(entryA.getCompressedBinaryContent()), Mode.UNCOMPRESS_NOWRAP);
92 | expected.write(entryA.getUncompressedBinaryContent());
93 |
94 | byte[] expectedBytes3 = new byte[] {6, 7, 8, 9, 0};
95 | stream.pipe(new ByteArrayInputStream(expectedBytes3), Mode.COPY);
96 | expected.write(expectedBytes3);
97 |
98 | stream.pipe(
99 | new ByteArrayInputStream(entryB.getCompressedBinaryContent()), Mode.UNCOMPRESS_NOWRAP);
100 | expected.write(entryB.getUncompressedBinaryContent());
101 |
102 | byte[] expectedBytes5 = new byte[] {127, 127, 127, 127, 127, 127};
103 | stream.pipe(new ByteArrayInputStream(expectedBytes5), Mode.COPY);
104 | expected.write(expectedBytes5);
105 |
106 | Assert.assertArrayEquals(expected.toByteArray(), outBuffer.toByteArray());
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/RandomAccessFileInputStreamFactoryTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import org.junit.After;
18 | import org.junit.Assert;
19 | import org.junit.Before;
20 | import org.junit.Test;
21 | import org.junit.runner.RunWith;
22 | import org.junit.runners.JUnit4;
23 |
24 | import java.io.File;
25 | import java.io.FileOutputStream;
26 | import java.io.IOException;
27 |
28 | /**
29 | * Tests for {@link RandomAccessFileInputStreamFactory}.
30 | */
31 | @RunWith(JUnit4.class)
32 | @SuppressWarnings("javadoc")
33 | public class RandomAccessFileInputStreamFactoryTest {
34 | /**
35 | * The object under test.
36 | */
37 | private RandomAccessFileInputStreamFactory factory = null;
38 |
39 | /**
40 | * Test data written to the file.
41 | */
42 | private byte[] testData = null;
43 |
44 | /**
45 | * The temp file.
46 | */
47 | private File tempFile = null;
48 |
49 | @Before
50 | public void setup() throws IOException {
51 | testData = new byte[128];
52 | for (int x = 0; x < 128; x++) {
53 | testData[x] = (byte) x;
54 | }
55 | tempFile = File.createTempFile("ra-fist", "tmp");
56 | FileOutputStream out = new FileOutputStream(tempFile);
57 | out.write(testData);
58 | out.flush();
59 | out.close();
60 | tempFile.deleteOnExit();
61 | factory = new RandomAccessFileInputStreamFactory(tempFile, 0, testData.length);
62 | }
63 |
64 | @After
65 | public void tearDown() {
66 | try {
67 | tempFile.delete();
68 | } catch (Exception ignored) {
69 | // Nothing to do
70 | }
71 | }
72 |
73 | @Test
74 | public void testNewStream_MakesIdenticalStreams() throws IOException {
75 | RandomAccessFileInputStream rafis1 = factory.newStream();
76 | RandomAccessFileInputStream rafis2 = factory.newStream();
77 | try {
78 | Assert.assertNotSame(rafis1, rafis2);
79 | for (int x = 0; x < testData.length; x++) {
80 | Assert.assertEquals(x, rafis1.read());
81 | Assert.assertEquals(x, rafis2.read());
82 | }
83 | Assert.assertEquals(-1, rafis1.read());
84 | Assert.assertEquals(-1, rafis2.read());
85 | } finally {
86 | try {
87 | rafis1.close();
88 | } catch (Exception ignored) {
89 | // Nothing
90 | }
91 | try {
92 | rafis2.close();
93 | } catch (Exception ignored) {
94 | // Nothing
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/RandomAccessFileOutputStreamTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import org.junit.After;
18 | import org.junit.Assert;
19 | import org.junit.Before;
20 | import org.junit.Test;
21 | import org.junit.runner.RunWith;
22 | import org.junit.runners.JUnit4;
23 |
24 | import java.io.DataInputStream;
25 | import java.io.File;
26 | import java.io.FileInputStream;
27 | import java.io.IOException;
28 | import java.io.RandomAccessFile;
29 |
30 | /**
31 | * Tests for {@link RandomAccessFileOutputStream}.
32 | */
33 | @RunWith(JUnit4.class)
34 | @SuppressWarnings("javadoc")
35 | public class RandomAccessFileOutputStreamTest {
36 | /**
37 | * The object under test.
38 | */
39 | private RandomAccessFileOutputStream stream = null;
40 |
41 | /**
42 | * Test data written to the file.
43 | */
44 | private byte[] testData = null;
45 |
46 | /**
47 | * The temp file.
48 | */
49 | private File tempFile = null;
50 |
51 | @Before
52 | public void setup() throws IOException {
53 | testData = new byte[128];
54 | for (int x = 0; x < 128; x++) {
55 | testData[x] = (byte) x;
56 | }
57 | tempFile = File.createTempFile("ra-fost", "tmp");
58 | tempFile.deleteOnExit();
59 | }
60 |
61 | @After
62 | public void tearDown() {
63 | try {
64 | stream.close();
65 | } catch (Exception ignored) {
66 | // Nothing to do
67 | }
68 | try {
69 | tempFile.delete();
70 | } catch (Exception ignored) {
71 | // Nothing to do
72 | }
73 | }
74 |
75 | @Test
76 | public void testCreateAndSize() throws IOException {
77 | stream = new RandomAccessFileOutputStream(tempFile, 11L);
78 | Assert.assertEquals(11, tempFile.length());
79 | }
80 |
81 | @Test(expected = IOException.class)
82 | public void testCreateAndFailToSize() throws IOException {
83 | stream =
84 | new RandomAccessFileOutputStream(tempFile, 11L) {
85 | @Override
86 | protected RandomAccessFile getRandomAccessFile(File file) throws IOException {
87 | return new RandomAccessFile(file, "rw") {
88 | @Override
89 | public void setLength(long newLength) throws IOException {
90 | // Do nothing, to trigger failure case in the constructor.
91 | }
92 | };
93 | }
94 | };
95 | }
96 |
97 | @Test
98 | public void testWrite() throws IOException {
99 | stream = new RandomAccessFileOutputStream(tempFile, 1L);
100 | stream.write(7);
101 | stream.flush();
102 | stream.close();
103 | FileInputStream in = null;
104 | try {
105 | in = new FileInputStream(tempFile);
106 | Assert.assertEquals(7, in.read());
107 | } finally {
108 | try {
109 | in.close();
110 | } catch (Exception ignored) {
111 | // Nothing
112 | }
113 | }
114 | }
115 |
116 | @Test
117 | public void testWriteArray() throws IOException {
118 | stream = new RandomAccessFileOutputStream(tempFile, 1L);
119 | stream.write(testData, 0, testData.length);
120 | stream.flush();
121 | stream.close();
122 | FileInputStream in = null;
123 | DataInputStream dataIn = null;
124 | try {
125 | in = new FileInputStream(tempFile);
126 | dataIn = new DataInputStream(in);
127 | byte[] actual = new byte[testData.length];
128 | dataIn.readFully(actual);
129 | Assert.assertArrayEquals(testData, actual);
130 | } finally {
131 | if (dataIn != null) {
132 | try {
133 | dataIn.close();
134 | } catch (Exception ignored) {
135 | // Nothing
136 | }
137 | }
138 | if (in != null) {
139 | try {
140 | in.close();
141 | } catch (Exception ignored) {
142 | // Nothing
143 | }
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/shared/src/test/java/com/google/archivepatcher/shared/TypedRangeTest.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.util.HashSet;
18 | import java.util.Set;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 | import org.junit.runners.JUnit4;
24 |
25 | /**
26 | * Tests for {@link TypedRange}.
27 | */
28 | @RunWith(JUnit4.class)
29 | @SuppressWarnings("javadoc")
30 | public class TypedRangeTest {
31 |
32 | @Test
33 | public void testGetters() {
34 | String text = "hello";
35 | TypedRange range = new TypedRange(555, 777, text);
36 | Assert.assertEquals(555, range.getOffset());
37 | Assert.assertEquals(777, range.getLength());
38 | Assert.assertSame(text, range.getMetadata());
39 | }
40 |
41 | @Test
42 | public void testToString() {
43 | // Just make sure this doesn't crash.
44 | TypedRange range = new TypedRange(555, 777, "woohoo");
45 | Assert.assertNotNull(range.toString());
46 | Assert.assertFalse(range.toString().length() == 0);
47 | }
48 |
49 | @Test
50 | public void testCompare() {
51 | TypedRange range1 = new TypedRange(1, 777, null);
52 | TypedRange range2 = new TypedRange(2, 777, null);
53 | Assert.assertTrue(range1.compareTo(range2) < 0);
54 | Assert.assertTrue(range2.compareTo(range1) > 0);
55 | Assert.assertTrue(range1.compareTo(range1) == 0);
56 | }
57 |
58 | @Test
59 | public void testHashCode() {
60 | TypedRange range1a = new TypedRange(123, 456, "hi mom");
61 | TypedRange range1b = new TypedRange(123, 456, "hi mom");
62 | Assert.assertEquals(range1a.hashCode(), range1b.hashCode());
63 | Set hashCodes = new HashSet();
64 | hashCodes.add(range1a.hashCode());
65 | hashCodes.add(new TypedRange(123 + 1, 456, "hi mom").hashCode()); // offset changed
66 | hashCodes.add(new TypedRange(123, 456 + 1, "hi mom").hashCode()); // length changed
67 | hashCodes.add(new TypedRange(123 + 1, 456, "x").hashCode()); // metadata changed
68 | hashCodes.add(new TypedRange(123 + 1, 456, null).hashCode()); // no metadata at all
69 | // Assert that all 4 hash codes are unique
70 | Assert.assertEquals(5, hashCodes.size());
71 | }
72 |
73 | @Test
74 | public void testEquals() {
75 | TypedRange range1a = new TypedRange(123, 456, "hi mom");
76 | Assert.assertEquals(range1a, range1a); // identity case
77 | TypedRange range1b = new TypedRange(123, 456, "hi mom");
78 | Assert.assertEquals(range1a, range1b); // equality case
79 | Assert.assertNotEquals(range1a, new TypedRange(123 + 1, 456, "hi mom")); // offset
80 | Assert.assertNotEquals(range1a, new TypedRange(123, 456 + 1, "hi mom")); // length
81 | Assert.assertNotEquals(range1a, new TypedRange(123, 456, "foo")); // metadata
82 | Assert.assertNotEquals(range1a, new TypedRange(123, 456, null)); // no metadata
83 | Assert.assertNotEquals(new TypedRange(123, 456, null), range1a); // other code branch
84 | Assert.assertEquals(
85 | new TypedRange(123, 456, null),
86 | new TypedRange(123, 456, null)); // both with null metadata
87 | Assert.assertNotEquals(range1a, null); // versus null
88 | Assert.assertNotEquals(range1a, "space channel 5"); // versus object of different class
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/sharedtest/build.gradle:
--------------------------------------------------------------------------------
1 | // sharedtest module
2 |
3 | apply plugin: 'java'
4 |
5 | dependencies {
6 | compile 'junit:junit:4.12'
7 | compile project(':shared')
8 | }
9 | // EOF
10 |
--------------------------------------------------------------------------------
/sharedtest/src/main/java/com/google/archivepatcher/shared/UnitTestZipEntry.java:
--------------------------------------------------------------------------------
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.shared;
16 |
17 | import java.io.ByteArrayInputStream;
18 | import java.io.ByteArrayOutputStream;
19 | import java.io.IOException;
20 | import java.io.UnsupportedEncodingException;
21 |
22 | /**
23 | * Data for one entry in the zip returned by {@link UnitTestZipArchive#makeTestZip()}.
24 | */
25 | public class UnitTestZipEntry {
26 | /**
27 | * The path under which the data is located in the archive.
28 | */
29 | public final String path;
30 |
31 | /**
32 | * The compression level of the entry.
33 | */
34 | public final int level;
35 |
36 | /**
37 | * The binary content of the entry.
38 | */
39 | public final String content;
40 |
41 | /**
42 | * Optional comment, as an ASCII string.
43 | */
44 | public final String comment;
45 |
46 | /**
47 | * Whether or not to use nowrap.
48 | */
49 | public final boolean nowrap;
50 |
51 | /**
52 | * Creates a new entry with nowrap=true.
53 | * @param path the path under which the data is located in the archive
54 | * @param level the compression level of the entry
55 | * @param content the binary content of the entry, as an ASCII string
56 | * @param comment optional comment, as an ASCII string
57 | */
58 | public UnitTestZipEntry(String path, int level, String content, String comment) {
59 | this(path, level, true, content, comment);
60 | }
61 |
62 | /**
63 | * Creates a new entry.
64 | *
65 | * @param path the path under which the data is located in the archive
66 | * @param level the compression level of the entry
67 | * @param nowrap the wrapping mode (false to wrap the entry like gzip, true otherwise)
68 | * @param content the binary content of the entry, as an ASCII string
69 | * @param comment optional comment, as an ASCII string
70 | */
71 | public UnitTestZipEntry(String path, int level, boolean nowrap, String content, String comment) {
72 | this.path = path;
73 | this.level = level;
74 | this.nowrap = nowrap;
75 | this.content = content;
76 | this.comment = comment;
77 | }
78 |
79 | /**
80 | * Returns the uncompressed content of the entry as a byte array for unit test simplicity. If the
81 | * level is 0, this is the same as the actual array of bytes that will be present in the zip
82 | * archive. If the level is not 0, this is the result of uncompressed the bytes that are present
83 | * in the zip archive for this entry.
84 | * @return as described
85 | */
86 | public byte[] getUncompressedBinaryContent() {
87 | try {
88 | return content.getBytes("US-ASCII");
89 | } catch (UnsupportedEncodingException e) {
90 | throw new RuntimeException("System doesn't support US-ASCII"); // Not likely
91 | }
92 | }
93 |
94 | /**
95 | * Returns the compressed form of the content, according to the level, that should be found in the
96 | * zip archive. If the level is 0 (store, i.e. not compressed) this is the same as calling
97 | * {@link #getUncompressedBinaryContent()}.
98 | * @return the content, as a byte array
99 | */
100 | public byte[] getCompressedBinaryContent() {
101 | if (level == 0) {
102 | return getUncompressedBinaryContent();
103 | }
104 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
105 | DeflateCompressor compressor = new DeflateCompressor();
106 | compressor.setCompressionLevel(level);
107 | compressor.setNowrap(nowrap);
108 | try {
109 | compressor.compress(new ByteArrayInputStream(getUncompressedBinaryContent()), buffer);
110 | } catch (IOException e) {
111 | throw new RuntimeException(e); // Shouldn't happen as this is all in-memory
112 | }
113 | return buffer.toByteArray();
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/tools/build.gradle:
--------------------------------------------------------------------------------
1 | // tools module
2 |
3 | apply plugin: 'java'
4 |
5 | def mainClassName = 'com.google.archivepatcher.tools.FileByFileTool'
6 | jar {
7 | manifest {
8 | attributes "Main-Class": mainClassName
9 | }
10 |
11 | from {
12 | configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
13 | }
14 | }
15 |
16 | dependencies {
17 | compile project(':applier')
18 | compile project(':explainer')
19 | compile project(':generator')
20 | compile project(':shared')
21 | }
22 | // EOF
23 |
--------------------------------------------------------------------------------
/tools/src/main/java/com/google/archivepatcher/tools/AbstractTool.java:
--------------------------------------------------------------------------------
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.tools;
16 |
17 | import java.io.File;
18 | import java.util.Iterator;
19 |
20 | /**
21 | * Simple base class for tools. Minimal standalone functionality free of third-party argument parser
22 | * dependencies.
23 | */
24 | public abstract class AbstractTool {
25 |
26 | /**
27 | * Pop an argument from the argument iterator or exit with a usage message about the expected
28 | * type of argument that was supposed to be found.
29 | * @param iterator the iterator to take an element from if available
30 | * @param expectedType description for the thing that was supposed to be in the iterator, for
31 | * error messages
32 | * @return the element retrieved from the iterator
33 | */
34 | protected String popOrDie(Iterator iterator, String expectedType) {
35 | if (!iterator.hasNext()) {
36 | exitWithUsage("missing argument for " + expectedType);
37 | }
38 | return iterator.next();
39 | }
40 |
41 | /**
42 | * Find and return a readable file if it exists, exit with a usage message if it does not.
43 | * @param path the path to check and get a {@link File} for
44 | * @param description what the file represents, for error messages
45 | * @return a {@link File} representing the path, which exists and is readable
46 | */
47 | protected File getRequiredFileOrDie(String path, String description) {
48 | File result = new File(path);
49 | if (!result.exists() || !result.canRead()) {
50 | exitWithUsage(description + " does not exist or cannot be read: " + path);
51 | }
52 | return result;
53 | }
54 |
55 | /**
56 | * Terminate the program with an error message and usage instructions.
57 | * @param message the error message to give to the user prior to the usage instructions
58 | */
59 | protected void exitWithUsage(String message) {
60 | System.err.println("Error: " + message);
61 | System.err.println(getUsage());
62 | System.exit(1);
63 | }
64 |
65 | /**
66 | * Returns a string describing the usage for this tool.
67 | * @return the string
68 | */
69 | protected abstract String getUsage();
70 | }
71 |
--------------------------------------------------------------------------------