diff --git a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/CompareFiles.java b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/CompareFiles.java index 3a34531..bc1ea2a 100644 --- a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/CompareFiles.java +++ b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/CompareFiles.java @@ -16,7 +16,7 @@ * * @author Daniel Tischner {@literal } */ -@SuppressWarnings({"UseOfSystemOutOrSystemErr", "ClassIndependentOfModule", "ClassOnlyUsedInOneModule"}) +@SuppressWarnings({ "UseOfSystemOutOrSystemErr", "ClassIndependentOfModule", "ClassOnlyUsedInOneModule" }) enum CompareFiles { ; diff --git a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/LocalChunkCache.java b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/LocalChunkCache.java index ac22dc3..efd7fd4 100644 --- a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/LocalChunkCache.java +++ b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/LocalChunkCache.java @@ -12,7 +12,7 @@ * * @author Daniel Tischner {@literal } */ -@SuppressWarnings({"UseOfSystemOutOrSystemErr", "ClassIndependentOfModule", "ClassOnlyUsedInOneModule"}) +@SuppressWarnings({ "UseOfSystemOutOrSystemErr", "ClassIndependentOfModule", "ClassOnlyUsedInOneModule" }) enum LocalChunkCache { ; @@ -20,6 +20,8 @@ enum LocalChunkCache { * Starts the application. * * @param args Two arguments, the path to the build and the path to the local chunk cache + * + * @throws IOException If an IOException occurred */ public static void main(final String[] args) throws IOException { if (args.length != 2) { diff --git a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchBenchmark.java b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchBenchmark.java index 8ce893c..252c6ef 100644 --- a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchBenchmark.java +++ b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchBenchmark.java @@ -9,6 +9,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Class offering a {@link #main(String[])} method that compares builds in a given folder with each other and creates @@ -16,12 +17,16 @@ * * @author Daniel Tischner {@literal } */ -@SuppressWarnings("UseOfSystemOutOrSystemErr") -final class PatchBenchmark { +@SuppressWarnings({ "UseOfSystemOutOrSystemErr", "MagicNumber" }) +enum PatchBenchmark { + ; + /** * Starts the application. * * @param args One arguments, the path to the builds to benchmark. + * + * @throws IOException If an IOException occurred */ public static void main(final String[] args) throws IOException { if (args.length != 1) { @@ -29,31 +34,34 @@ public static void main(final String[] args) throws IOException { "Expected one arguments denoting the path to the folder containing the builds to benchmark."); } - Path basePath = Path.of(args[0]); - List builds = Files.list(basePath) - .filter(Files::isDirectory) - .map(Path::getFileName) - .map(Path::toString) - .sorted() - .collect(Collectors.toList()); + final Path basePath = Path.of(args[0]); + final List builds; + try (final Stream stream = Files.list(basePath)) { + builds = stream.filter(Files::isDirectory) + .map(Path::getFileName) + .map(Path::toString) + .sorted() + .collect(Collectors.toList()); + } - List> buildsToCompare = new ArrayList<>(); + final List> buildsToCompare = new ArrayList<>(); for (int i = 0; i < builds.size() - 1; i++) { - String previous = builds.get(i); - String current = builds.get(i + 1); + final String previous = builds.get(i); + final String current = builds.get(i + 1); buildsToCompare.add(Map.entry(previous, current)); } System.out.printf("Comparing %d patch scenarios%n", buildsToCompare.size()); - List patchDataLines = new ArrayList<>(); + final Collection patchDataLines = new ArrayList<>(); patchDataLines.add("patch,name,fsc2mb,fastcdc2mb,fastcdc8kb"); // patchDataLines.add("patch,name,rtpal262kb"); - List buildDataLines = new ArrayList<>(); + final Collection buildDataLines = new ArrayList<>(); buildDataLines.add("version,name,size"); int i = 1; - for (Map.Entry comparison : buildsToCompare) { + for (final Map.Entry comparison : buildsToCompare) { + //noinspection HardcodedFileSeparator System.out.printf("====================== %d / %d patch scenarios ======================%n", i, buildsToCompare.size()); @@ -76,11 +84,11 @@ public static void main(final String[] args) throws IOException { comparison.getValue()); System.out.println(); - List patchSizes = new ArrayList<>(); - AtomicLong previousBuildSize = new AtomicLong(); - AtomicLong currentBuildSize = new AtomicLong(); + final Collection patchSizes = new ArrayList<>(); + final AtomicLong previousBuildSize = new AtomicLong(); + final AtomicLong currentBuildSize = new AtomicLong(); descriptionToChunker.forEach((description, chunker) -> { - PatchSummary summary = PatchSummary.computePatchSummary(chunker, previousBuild, currentBuild); + final PatchSummary summary = PatchSummary.computePatchSummary(chunker, previousBuild, currentBuild); patchSizes.add(summary.getPatchSize()); @@ -90,16 +98,14 @@ public static void main(final String[] args) throws IOException { .getTotalSize()); }); - StringJoiner patchSizesJoiner = new StringJoiner(","); + final StringJoiner patchSizesJoiner = new StringJoiner(","); patchSizesJoiner.add(String.valueOf(i + 1)); - patchSizesJoiner.add(previousBuild.getFileName() - .toString() + "-" + currentBuild.getFileName() - .toString()); + patchSizesJoiner.add(previousBuild.getFileName() + "-" + currentBuild.getFileName()); patchSizes.forEach(size -> patchSizesJoiner.add(String.valueOf(size))); patchDataLines.add(patchSizesJoiner.toString()); if (i == 1) { - StringJoiner buildDataJoiner = new StringJoiner(","); + final StringJoiner buildDataJoiner = new StringJoiner(","); buildDataJoiner.add("1"); buildDataJoiner.add(previousBuild.getFileName() .toString()); @@ -107,7 +113,7 @@ public static void main(final String[] args) throws IOException { buildDataLines.add(buildDataJoiner.toString()); } - StringJoiner buildDataJoiner = new StringJoiner(","); + final StringJoiner buildDataJoiner = new StringJoiner(","); buildDataJoiner.add(String.valueOf(i + 1)); buildDataJoiner.add(currentBuild.getFileName() .toString()); @@ -116,8 +122,8 @@ public static void main(final String[] args) throws IOException { i++; - Path buildDataPath = Path.of("benchmark_build_data.csv"); - Path patchDataPath = Path.of("benchmark_patch_data.csv"); + final Path buildDataPath = Path.of("benchmark_build_data.csv"); + final Path patchDataPath = Path.of("benchmark_patch_data.csv"); Files.write(buildDataPath, buildDataLines); Files.write(patchDataPath, patchDataLines); System.out.println("Updated benchmark build data: " + buildDataPath); diff --git a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchSummary.java b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchSummary.java index 9c9314e..b786188 100644 --- a/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchSummary.java +++ b/de.zabuza.fastcdc4j.examples/src/de/zabuza/fastcdc4j/examples/PatchSummary.java @@ -26,7 +26,7 @@ * * @author Daniel Tischner {@literal } */ -@SuppressWarnings("UseOfSystemOutOrSystemErr") +@SuppressWarnings({ "UseOfSystemOutOrSystemErr", "MagicNumber" }) final class PatchSummary { /** * Starts the application. @@ -104,41 +104,41 @@ public static void main(final String[] args) { static PatchSummary computePatchSummary(final Chunker chunker, final Path previousBuild, final Path currentBuild) { final List previousChunks = Collections.synchronizedList(new ArrayList<>()); - chunkPath(chunker, previousBuild, chunk -> previousChunks.add(chunk.toChunkMetadata())); + PatchSummary.chunkPath(chunker, previousBuild, chunk -> previousChunks.add(chunk.toChunkMetadata())); final BuildSummary previousBuildSummary = new BuildSummary(previousChunks); final List currentChunks = Collections.synchronizedList(new ArrayList<>()); - chunkPath(chunker, currentBuild, chunk -> currentChunks.add(chunk.toChunkMetadata())); + PatchSummary.chunkPath(chunker, currentBuild, chunk -> currentChunks.add(chunk.toChunkMetadata())); final BuildSummary currentBuildSummary = new BuildSummary(currentChunks); return new PatchSummary(previousBuildSummary, currentBuildSummary); } - static void executePatchSummary(final String description, final Chunker chunker, final Path previousBuild, + private static void executePatchSummary(final String description, final Chunker chunker, final Path previousBuild, final Path currentBuild) { - final PatchSummary summary = computePatchSummary(chunker, previousBuild, currentBuild); + final PatchSummary summary = PatchSummary.computePatchSummary(chunker, previousBuild, currentBuild); System.out.println("==== " + description); System.out.printf("%-25s %12s total size, %12d total chunks, %12s unique size, %12d unique chunks%n", - "Build summary previous:", bytesToReadable(summary.getPreviousBuildSummary() + "Build summary previous:", PatchSummary.bytesToReadable(summary.getPreviousBuildSummary() .getTotalSize()), summary.getPreviousBuildSummary() - .getTotalChunksCount(), bytesToReadable(summary.getPreviousBuildSummary() + .getTotalChunksCount(), PatchSummary.bytesToReadable(summary.getPreviousBuildSummary() .getTotalUniqueSize()), summary.getPreviousBuildSummary() .getUniqueChunksCount()); System.out.printf("%-25s %12s total size, %12d total chunks, %12s unique size, %12d unique chunks%n", - "Build summary current:", bytesToReadable(summary.getCurrentBuildSummary() + "Build summary current:", PatchSummary.bytesToReadable(summary.getCurrentBuildSummary() .getTotalSize()), summary.getCurrentBuildSummary() - .getTotalChunksCount(), bytesToReadable(summary.getCurrentBuildSummary() + .getTotalChunksCount(), PatchSummary.bytesToReadable(summary.getCurrentBuildSummary() .getTotalUniqueSize()), summary.getCurrentBuildSummary() .getUniqueChunksCount()); System.out.printf("%-25s %12s average chunk size, %12.2f%% deduplication ratio%n", "Build metrics previous:", - bytesToReadable(summary.getPreviousBuildSummary() + PatchSummary.bytesToReadable(summary.getPreviousBuildSummary() .getAverageChunkSize()), summary.getPreviousBuildSummary() .getDeduplicationRatio()); System.out.printf("%-25s %12s average chunk size, %12.2f%% deduplication ratio%n", "Build metrics current:", - bytesToReadable(summary.getCurrentBuildSummary() + PatchSummary.bytesToReadable(summary.getCurrentBuildSummary() .getAverageChunkSize()), summary.getCurrentBuildSummary() .getDeduplicationRatio()); - System.out.printf("%-25s %12s%n", "Patch size:", bytesToReadable(summary.getPatchSize())); + System.out.printf("%-25s %12s%n", "Patch size:", PatchSummary.bytesToReadable(summary.getPatchSize())); System.out.printf("%-25s %12d%n", "Chunks to add:", summary.getChunksToAdd() .size()); System.out.printf("%-25s %12d%n", "Chunks to remove:", summary.getChunksToRemove() @@ -150,66 +150,71 @@ static void executePatchSummary(final String description, final Chunker chunker, System.out.println(); } - private static String bytesToReadable(long bytes) { + private static String bytesToReadable(final long bytes) { if (bytes < 1_000) { return bytes + " B"; } - double kiloBytes = bytes / 1_000.0; + final double kiloBytes = bytes / 1_000.0; if (kiloBytes < 1_000) { return String.format("%.2f", kiloBytes) + " KB"; } - double megaBytes = kiloBytes / 1_000.0; + final double megaBytes = kiloBytes / 1_000.0; if (megaBytes < 1_000) { return String.format("%.2f", megaBytes) + " MB"; } - double gigaBytes = megaBytes / 1_000.0; + final double gigaBytes = megaBytes / 1_000.0; if (gigaBytes < 1_000) { return String.format("%.2f", gigaBytes) + " GB"; } return ""; } - private static void chunkPath(final Chunker chunker, final Path path, final Consumer chunkAction) { + private static void chunkPath(final Chunker chunker, final Path path, final Consumer chunkAction) { try { - List files = Files.walk(path) - .filter(Files::isRegularFile) - .collect(Collectors.toList()); - - long totalBytes = files.stream() + final List files; + //noinspection NestedTryStatement + try (final Stream stream = Files.walk(path)) { + files = stream.filter(Files::isRegularFile) + .collect(Collectors.toList()); + } + + final long totalBytes = files.stream() .mapToLong(file -> { try { return Files.size(file); - } catch (IOException e) { + } catch (final IOException e) { throw new UncheckedIOException(e); } }) .sum(); - AtomicLong processedBytesTotal = new AtomicLong(0); - AtomicLong processedBytesSincePrint = new AtomicLong(0); - AtomicLong timeStart = new AtomicLong(System.nanoTime()); - ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); + final AtomicLong processedBytesTotal = new AtomicLong(0); + final AtomicLong processedBytesSincePrint = new AtomicLong(0); + final AtomicLong timeStart = new AtomicLong(System.nanoTime()); + final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); final long nanosPerSecond = 1_000_000_000L; - Runnable statPrinter = () -> { - AtomicLong timeEnd = new AtomicLong(System.nanoTime()); - long timeDiff = timeEnd.get() - timeStart.get(); + final Runnable statPrinter = () -> { + final AtomicLong timeEnd = new AtomicLong(System.nanoTime()); + final long timeDiff = timeEnd.get() - timeStart.get(); if (timeDiff < nanosPerSecond) { return; } timeStart.set(timeEnd.get()); - long bytesPerSecond = processedBytesSincePrint.get() / (timeDiff / nanosPerSecond); - long bytesLeft = totalBytes - processedBytesTotal.get(); - long secondsLeft = bytesLeft / (bytesPerSecond == 0 ? 1 : bytesPerSecond); + final long bytesPerSecond = processedBytesSincePrint.get() / (timeDiff / nanosPerSecond); + final long bytesLeft = totalBytes - processedBytesTotal.get(); + final long secondsLeft = bytesLeft / (bytesPerSecond == 0 ? 1 : bytesPerSecond); - System.out.printf("\t%12s/s, %12s ETC, %12s processed, %12s total\r", bytesToReadable(bytesPerSecond), - secondsToReadable(secondsLeft), bytesToReadable(processedBytesTotal.get()), - bytesToReadable(totalBytes)); + //noinspection HardcodedLineSeparator + System.out.printf("\t%12s/s, %12s ETC, %12s processed, %12s total\r", + PatchSummary.bytesToReadable(bytesPerSecond), PatchSummary.secondsToReadable(secondsLeft), + PatchSummary.bytesToReadable(processedBytesTotal.get()), + PatchSummary.bytesToReadable(totalBytes)); processedBytesSincePrint.set(0); }; - var statPrintTask = service.scheduleAtFixedRate(statPrinter, 0, 1, TimeUnit.SECONDS); + final var statPrintTask = service.scheduleAtFixedRate(statPrinter, 0, 1, TimeUnit.SECONDS); files.parallelStream() .filter(Files::isRegularFile) @@ -222,31 +227,31 @@ private static void chunkPath(final Chunker chunker, final Path path, final Cons })); statPrintTask.cancel(false); service.shutdown(); - } catch (IOException e) { + } catch (final IOException e) { throw new UncheckedIOException(e); } } - private static String secondsToReadable(long seconds) { - StringBuilder sb = new StringBuilder(); + private static String secondsToReadable(final long seconds) { + final StringBuilder sb = new StringBuilder(); boolean entered = false; - Duration time = Duration.ofSeconds(seconds); + final Duration time = Duration.ofSeconds(seconds); - long days = time.toDays(); + final long days = time.toDays(); if (days != 0) { sb.append(days) .append("d "); entered = true; } - int hours = time.toHoursPart(); + final int hours = time.toHoursPart(); if (hours != 0 || entered) { sb.append(hours) .append("h "); entered = true; } - int minutes = time.toMinutesPart(); + final int minutes = time.toMinutesPart(); if (minutes != 0 || entered) { sb.append(minutes) .append("m "); @@ -333,7 +338,7 @@ static final class BuildSummary { private long totalUniqueSize; private int uniqueChunksCount; - public BuildSummary(final Iterable chunks) { + BuildSummary(final Iterable chunks) { chunks.forEach(chunk -> { totalChunksCount++; totalSize += chunk.getLength(); @@ -347,41 +352,41 @@ public BuildSummary(final Iterable chunks) { }); } - public boolean containsChunk(final ChunkMetadata chunk) { + public long getTotalSize() { + return totalSize; + } + + boolean containsChunk(final ChunkMetadata chunk) { return hashToChunk.containsKey(chunk.getHexHash()); } - public int getAverageChunkSize() { + int getAverageChunkSize() { //noinspection NumericCastThatLosesPrecision return (int) (totalSize / totalChunksCount); } - public ChunkMetadata getChunk(final String hash) { + ChunkMetadata getChunk(final String hash) { return hashToChunk.get(hash); } - public Stream getChunks() { + Stream getChunks() { return hashToChunk.values() .stream(); } - public double getDeduplicationRatio() { + double getDeduplicationRatio() { return (double) totalUniqueSize / totalSize * 100; } - public int getTotalChunksCount() { + int getTotalChunksCount() { return totalChunksCount; } - public long getTotalSize() { - return totalSize; - } - - public long getTotalUniqueSize() { + long getTotalUniqueSize() { return totalUniqueSize; } - public int getUniqueChunksCount() { + int getUniqueChunksCount() { return uniqueChunksCount; } } diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/Chunker.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/Chunker.java index eb644c9..aed1018 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/Chunker.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/Chunker.java @@ -1,10 +1,12 @@ package de.zabuza.fastcdc4j.external.chunking; import de.zabuza.fastcdc4j.internal.util.FlatIterator; +import de.zabuza.fastcdc4j.internal.util.Validations; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Objects; import java.util.stream.Stream; /** @@ -22,9 +24,9 @@ public interface Chunker { *

* Chunks own their bytes, so it is preferable to process them directly and avoid first collecting all of them. * - * @param stream The data stream to chunk + * @param stream The data stream to chunk, not null * @param size The amount of bytes available in the stream that are subject to be chunked, the stream must offer - * at least that many bytes. + * at least that many bytes. Must be positive and not zero. * * @return The chunks of the stream, lazily populated */ @@ -38,11 +40,12 @@ public interface Chunker { *

* The stream is consumed sequential, files are not processed parallel. * - * @param paths Stream of files to process, only regular files are chunked + * @param paths Stream of files to process, only regular files are chunked, not null * * @return The chunks of the stream, lazily populated */ default Iterable chunk(final Stream paths) { + Objects.requireNonNull(paths); return () -> new FlatIterator<>(paths.filter(Files::isRegularFile) .iterator(), path -> chunk(path).iterator()); } @@ -51,11 +54,13 @@ default Iterable chunk(final Stream paths) { * Chunks the given data into chunks. The data is consumed and populates the resulting iterable lazily as it is * consumed. * - * @param data The data to chunk + * @param data The data to chunk, not null and not empty * * @return The chunks of the stream, lazily populated */ default Iterable chunk(final byte[] data) { + Objects.requireNonNull(data); + Validations.require(data.length > 0, "Data must not be empty"); return chunk(new ByteArrayInputStream(data), data.length); } @@ -69,11 +74,12 @@ default Iterable chunk(final byte[] data) { *

* The stream is consumed sequential, files are not processed parallel. * - * @param path Either a regular file or a directory to traverse, only regular files are processed + * @param path Either a regular file or a directory to traverse, only regular files are processed, not null * * @return The chunks of the stream, lazily populated */ default Iterable chunk(final Path path) { + Objects.requireNonNull(path); try { if (Files.isDirectory(path)) { return chunk(Files.walk(path)); diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/ChunkerBuilder.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/ChunkerBuilder.java index a60bc4a..c5f30b6 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/ChunkerBuilder.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/ChunkerBuilder.java @@ -1,9 +1,11 @@ package de.zabuza.fastcdc4j.external.chunking; import de.zabuza.fastcdc4j.internal.chunking.*; +import de.zabuza.fastcdc4j.internal.util.Validations; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Objects; /** * Builder for convenient construction of {@link Chunker} instances. @@ -147,7 +149,6 @@ public final class ChunkerBuilder { * @return A chunker using the set properties */ public Chunker build() { - // TODO Add argument validation to all code // TODO Maybe add Adler and Rabin CDC alternatives if (chunker != null) { return chunker; @@ -236,7 +237,7 @@ public ChunkerBuilder nlFiedlerRust() { * @return This builder instance */ public ChunkerBuilder setChunker(final Chunker chunker) { - this.chunker = chunker; + this.chunker = Objects.requireNonNull(chunker); return this; } @@ -249,7 +250,7 @@ public ChunkerBuilder setChunker(final Chunker chunker) { * @return This builder instance */ public ChunkerBuilder setChunkerCore(final IterativeStreamChunkerCore chunkerCore) { - this.chunkerCore = chunkerCore; + this.chunkerCore = Objects.requireNonNull(chunkerCore); return this; } @@ -261,7 +262,7 @@ public ChunkerBuilder setChunkerCore(final IterativeStreamChunkerCore chunkerCor * @return This builder instance */ public ChunkerBuilder setChunkerOption(final ChunkerOption chunkerOption) { - this.chunkerOption = chunkerOption; + this.chunkerOption = Objects.requireNonNull(chunkerOption); return this; } @@ -273,10 +274,7 @@ public ChunkerBuilder setChunkerOption(final ChunkerOption chunkerOption) { * @return This builder instance */ public ChunkerBuilder setExpectedChunkSize(final int expectedChunkSize) { - if (expectedChunkSize < 0) { - throw new IllegalArgumentException("Expected chunk size must be positive, was: " + expectedChunkSize); - } - this.expectedChunkSize = expectedChunkSize; + this.expectedChunkSize = Validations.requirePositiveNonZero(expectedChunkSize, "Expected chunk size"); return this; } @@ -289,6 +287,7 @@ public ChunkerBuilder setExpectedChunkSize(final int expectedChunkSize) { * @return This builder instance */ public ChunkerBuilder setHashMethod(final String hashMethod) { + Objects.requireNonNull(hashMethod); try { MessageDigest.getInstance(hashMethod); } catch (final NoSuchAlgorithmException e) { @@ -307,11 +306,10 @@ public ChunkerBuilder setHashMethod(final String hashMethod) { * @return This builder instance */ public ChunkerBuilder setHashTable(final long[] hashTable) { + Objects.requireNonNull(hashTable); //noinspection MagicNumber - if (hashTable.length != 256) { - throw new IllegalArgumentException( - "Hash table must have a length of 256, one hash per byte value, was: " + hashTable.length); - } + Validations.require(hashTable.length == 256, + "Hash table must have a length of 256, one hash per byte value, was: " + hashTable.length); this.hashTable = hashTable.clone(); return this; } @@ -324,7 +322,7 @@ public ChunkerBuilder setHashTable(final long[] hashTable) { * @return This builder instance */ public ChunkerBuilder setHashTableOption(final HashTableOption hashTableOption) { - this.hashTableOption = hashTableOption; + this.hashTableOption = Objects.requireNonNull(hashTableOption); return this; } @@ -360,7 +358,7 @@ public ChunkerBuilder setMaskLarge(final long maskLarge) { * @return This builder instance */ public ChunkerBuilder setMaskOption(final MaskOption maskOption) { - this.maskOption = maskOption; + this.maskOption = Objects.requireNonNull(maskOption); return this; } @@ -379,11 +377,12 @@ public ChunkerBuilder setMaskSmall(final long maskSmall) { /** * Sets the factor to apply to the expected chunk size to receive the maximal chunk size. * - * @param maximalChunkSizeFactor The factor to apply + * @param maximalChunkSizeFactor The factor to apply, must be greater equals 1.0 * * @return This builder instance */ public ChunkerBuilder setMaximalChunkSizeFactor(final double maximalChunkSizeFactor) { + Validations.require(maximalChunkSizeFactor >= 1.0, "Maximal chunk size factor must be greater equals 1.0"); this.maximalChunkSizeFactor = maximalChunkSizeFactor; return this; } @@ -391,11 +390,12 @@ public ChunkerBuilder setMaximalChunkSizeFactor(final double maximalChunkSizeFac /** * Sets the factor to apply to the expected chunk size to receive the minimal chunk size. * - * @param minimalChunkSizeFactor The factor to apply + * @param minimalChunkSizeFactor The factor to apply, must be smaller equals 1.0 * * @return This builder instance */ public ChunkerBuilder setMinimalChunkSizeFactor(final double minimalChunkSizeFactor) { + Validations.require(minimalChunkSizeFactor <= 1.0, "Minimal chunk size factor must be smaller equals 1.0"); this.minimalChunkSizeFactor = minimalChunkSizeFactor; return this; } @@ -403,12 +403,13 @@ public ChunkerBuilder setMinimalChunkSizeFactor(final double minimalChunkSizeFac /** * Sets the normalization level used for choosing the masks in certain chunkers. * - * @param normalizationLevel The normalization level to use for choosing the masks in certain chunkers. + * @param normalizationLevel The normalization level to use for choosing the masks in certain chunkers, must be + * positive. * * @return This builder instance */ public ChunkerBuilder setNormalizationLevel(final int normalizationLevel) { - this.normalizationLevel = normalizationLevel; + this.normalizationLevel = Validations.requirePositive(normalizationLevel, "Normalization level"); return this; } } diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/IterativeStreamChunkerCore.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/IterativeStreamChunkerCore.java index 8dcaa74..9be355a 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/IterativeStreamChunkerCore.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/external/chunking/IterativeStreamChunkerCore.java @@ -13,9 +13,11 @@ public interface IterativeStreamChunkerCore { * Reads the next chunk from the given data stream. The stream is consumed by exactly the length of the provided * chunk. The stream is safe to be read byte by byte, it provides buffering methods if necessary. * - * @param stream The data stream to chunk - * @param size Remaining data available in the stream that are subject to be chunked - * @param currentOffset Current offset in the given stream, in regards to its original start + * @param stream The data stream to chunk, not null + * @param size Remaining data available in the stream that are subject to be chunked, must be positive and + * not zero + * @param currentOffset Current offset in the given stream, in regards to its original start, must be positive and + * less than size * * @return The chunk that was read */ diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FastCdcChunkerCore.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FastCdcChunkerCore.java index ecc849f..12ff19a 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FastCdcChunkerCore.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FastCdcChunkerCore.java @@ -1,11 +1,13 @@ package de.zabuza.fastcdc4j.internal.chunking; import de.zabuza.fastcdc4j.external.chunking.IterativeStreamChunkerCore; +import de.zabuza.fastcdc4j.internal.util.Validations; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.util.Objects; /** * Implementation of an iterative stream chunker core that chunks according to the FastCDC algorithm (by Wen Xia et al. @@ -43,11 +45,13 @@ public final class FastCdcChunkerCore implements IterativeStreamChunkerCore { /** * Creates a new core. * - * @param expectedSize The expected size for a single chunk, in bytes - * @param minSize The minimal size for a single chunk, in bytes - * @param maxSize The maximal size for a single chunk, in bytes + * @param expectedSize The expected size for a single chunk, in bytes, must be positive + * @param minSize The minimal size for a single chunk, in bytes, must be positive and less equals expected + * size + * @param maxSize The maximal size for a single chunk, in bytes, must be positive and greater equals expected + * size * @param gear The hash table, also known as {@code gear} used as noise to improve the splitting behavior - * for relatively similar content + * for relatively similar content, must have a length of exactly 256, one hash per byte value * @param maskSmall Mask for the fingerprint that is used for smaller windows, to decrease the likelihood of a * split * @param maskLarge Mask for the fingerprint that is used for bigger windows, to increase the likelihood of a @@ -56,9 +60,16 @@ public final class FastCdcChunkerCore implements IterativeStreamChunkerCore { @SuppressWarnings("ConstructorWithTooManyParameters") public FastCdcChunkerCore(final int expectedSize, final int minSize, final int maxSize, final long[] gear, final long maskSmall, final long maskLarge) { - this.expectedSize = expectedSize; - this.minSize = minSize; - this.maxSize = maxSize; + Validations.require(minSize <= expectedSize, "Min size must be less equals expected size"); + Validations.require(maxSize >= expectedSize, "Max size must be greater equals expected size"); + Objects.requireNonNull(gear); + //noinspection MagicNumber + Validations.require(gear.length == 256, + "Gear must have a length of 256, one hash per byte value, was: " + gear.length); + + this.expectedSize = Validations.requirePositive(expectedSize, "Expected size"); + this.minSize = Validations.requirePositive(minSize, "Min size"); + this.maxSize = Validations.requirePositive(maxSize, "Max size"); this.gear = gear.clone(); this.maskSmall = maskSmall; this.maskLarge = maskLarge; @@ -66,6 +77,11 @@ public FastCdcChunkerCore(final int expectedSize, final int minSize, final int m @Override public byte[] readNextChunk(final InputStream stream, final long size, final long currentOffset) { + Objects.requireNonNull(stream); + Validations.requirePositiveNonZero(size, "Size"); + Validations.requirePositive(currentOffset, "Current offset"); + Validations.require(currentOffset < size, "Current offset must be less than size"); + try (final ByteArrayOutputStream dataBuffer = new ByteArrayOutputStream()) { int normalSize = expectedSize; //noinspection StandardVariableNames diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FixedSizeChunkerCore.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FixedSizeChunkerCore.java index c7118f9..0d7dda8 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FixedSizeChunkerCore.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/FixedSizeChunkerCore.java @@ -1,10 +1,12 @@ package de.zabuza.fastcdc4j.internal.chunking; import de.zabuza.fastcdc4j.external.chunking.IterativeStreamChunkerCore; +import de.zabuza.fastcdc4j.internal.util.Validations; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.util.Objects; /** * Implementation of an iterative stream chunker core that splits data into chunks of equal size, known as {@code @@ -21,14 +23,19 @@ public final class FixedSizeChunkerCore implements IterativeStreamChunkerCore { /** * Creates a new core. * - * @param chunkSize The fixed chunk size to use for splitting + * @param chunkSize The fixed chunk size to use for splitting, must be positive and not zero */ public FixedSizeChunkerCore(final int chunkSize) { - this.chunkSize = chunkSize; + this.chunkSize = Validations.requirePositiveNonZero(chunkSize, "Chunk size"); } @Override public byte[] readNextChunk(final InputStream stream, final long size, final long currentOffset) { + Objects.requireNonNull(stream); + Validations.requirePositiveNonZero(size, "Size"); + Validations.requirePositive(currentOffset, "Current offset"); + Validations.require(currentOffset < size, "Current offset must be less than size"); + // Read up to CHUNK_SIZE many bytes //noinspection NumericCastThatLosesPrecision final int length = currentOffset + chunkSize <= size ? chunkSize : (int) (size - currentOffset); diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/HashTables.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/HashTables.java index 0e8855d..4f5cb44 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/HashTables.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/HashTables.java @@ -13,7 +13,7 @@ public enum HashTables { */ @SuppressWarnings("OverlyLargePrimitiveArrayInitializer") private static final long[] NLFIEDLER_RUST = - {0x5c95c078L, 0x22408989L, 0x2d48a214L, 0x12842087L, 0x530f8afbL, 0x474536b9L, 0x2963b4f1L, 0x44cb738bL, + { 0x5c95c078L, 0x22408989L, 0x2d48a214L, 0x12842087L, 0x530f8afbL, 0x474536b9L, 0x2963b4f1L, 0x44cb738bL, 0x4ea7403dL, 0x4d606b6eL, 0x074ec5d3L, 0x3af39d18L, 0x726003caL, 0x37a62a74L, 0x51a2f58eL, 0x7506358eL, 0x5d4ab128L, 0x4d4ae17bL, 0x41e85924L, 0x470c36f7L, 0x4741cbe1L, 0x01bb7f30L, 0x617c1de3L, 0x2b0c3a1fL, 0x50c48f73L, 0x21a82d37L, 0x6095ace0L, 0x419167a0L, 0x3caf49b0L, @@ -49,13 +49,13 @@ public enum HashTables { 0x0e93e12bL, 0x64b2791dL, 0x440d2476L, 0x588ea8ddL, 0x4665a658L, 0x7446c418L, 0x1877a774L, 0x5626407eL, 0x7f63bd46L, 0x32d2dbd8L, 0x3c790f4aL, 0x772b7239L, 0x6f8b2826L, 0x677ff609L, 0x0dc82c11L, 0x23ffe354L, 0x2eac53a6L, 0x16139e09L, 0x0afd0dbcL, 0x2a4d4237L, 0x56a368c7L, - 0x234325e4L, 0x2dce9187L, 0x32e8ea7eL}; + 0x234325e4L, 0x2dce9187L, 0x32e8ea7eL }; /** * Table used by RTPal. */ @SuppressWarnings("OverlyLargePrimitiveArrayInitializer") private static final long[] RTPAL = - {0x5a16b18f2aac863eL, 0x05fad735784f09eaL, 0x355c6a3868fe64afL, 0x57df89c95716c702L, 0x46ea7572135544a6L, + { 0x5a16b18f2aac863eL, 0x05fad735784f09eaL, 0x355c6a3868fe64afL, 0x57df89c95716c702L, 0x46ea7572135544a6L, 0x6291d5376cd79d73L, 0x2a6e072b609b0bbfL, 0x110f7f895ec438b7L, 0x2fc580f60659f690L, 0x15ce33c924a8880bL, 0x1f3fabc44c091f5fL, 0x76e7512d0f53c142L, 0x30ff6d65448b44b3L, 0x16db576e7ecfe3c9L, 0x7009bea841de2e20L, 0x0ad460d80f3fe181L, 0x0a1e6fed6ece42dbL, @@ -118,7 +118,7 @@ public enum HashTables { 0x45e90bc2494ad436L, 0x5291bcf62f0b6bdbL, 0x72ea193619f06853L, 0x5a5a2bd77114b311L, 0x5445faa82e02e158L, 0x0065712926726beaL, 0x1bed3b9a62fbf757L, 0x1767b815257b83d4L, 0x000eab4e77327b81L, 0x0fd333301966ff16L, 0x6780eb8339b83286L, 0x7652a5e647799673L, - 0x43c0db665e364315L, 0x6fe4fe01606d405dL, 0x6833dbd876b03920L}; + 0x43c0db665e364315L, 0x6fe4fe01606d405dL, 0x6833dbd876b03920L }; /** * Gets the table used by Nlfiedler-Rust diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/IterativeStreamChunker.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/IterativeStreamChunker.java index 0128e34..0c18531 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/IterativeStreamChunker.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/IterativeStreamChunker.java @@ -4,10 +4,14 @@ import de.zabuza.fastcdc4j.external.chunking.Chunker; import de.zabuza.fastcdc4j.external.chunking.IterativeStreamChunkerCore; import de.zabuza.fastcdc4j.internal.util.Util; +import de.zabuza.fastcdc4j.internal.util.Validations; import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; /** * Implementation of a chunker that iteratively chunks the stream by using a given {@link IterativeStreamChunkerCore} as @@ -28,17 +32,26 @@ public final class IterativeStreamChunker implements Chunker { /** * Creates a new chunker. * - * @param core The core to use for chunking. + * @param core The core to use for chunking, not null * @param hashMethod The hash method to use for hashing the data of a chunk, has to be supported and accepted by * {@link java.security.MessageDigest} */ public IterativeStreamChunker(final IterativeStreamChunkerCore core, final String hashMethod) { - this.core = core; + Objects.requireNonNull(hashMethod); + try { + MessageDigest.getInstance(hashMethod); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException("The given hash method is not supported, was: " + hashMethod, e); + } + + this.core = Objects.requireNonNull(core); this.hashMethod = hashMethod; } @Override public Iterable chunk(final InputStream stream, final long size) { + Objects.requireNonNull(stream); + Validations.requirePositiveNonZero(size, "Size"); return () -> new ChunkerIterator(stream, size, core, hashMethod); } @@ -70,18 +83,25 @@ private static final class ChunkerIterator implements Iterator { private long currentOffset; /** - * @param stream The data stream to chunk + * @param stream The data stream to chunk, not null * @param size The amount of bytes available in the stream that are subject to be chunked, the stream must - * offer at least that many bytes - * @param core The core to use for chunking. + * offer at least that many bytes, positive and not zero + * @param core The core to use for chunking, not null * @param hashMethod The hash method to use for hashing the data of a chunk, has to be supported and accepted by * {@link java.security.MessageDigest} */ private ChunkerIterator(final InputStream stream, final long size, final IterativeStreamChunkerCore core, final String hashMethod) { - this.stream = stream; - this.size = size; - this.core = core; + Objects.requireNonNull(hashMethod); + try { + MessageDigest.getInstance(hashMethod); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException("The given hash method is not supported, was: " + hashMethod, e); + } + + this.stream = Objects.requireNonNull(stream); + this.size = Validations.requirePositiveNonZero(size, "Size"); + this.core = Objects.requireNonNull(core); this.hashMethod = hashMethod; } diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/MaskGenerator.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/MaskGenerator.java index 5e92721..ac7c6b7 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/MaskGenerator.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/MaskGenerator.java @@ -2,11 +2,9 @@ import de.zabuza.fastcdc4j.external.chunking.MaskOption; import de.zabuza.fastcdc4j.internal.util.Util; +import de.zabuza.fastcdc4j.internal.util.Validations; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -24,12 +22,14 @@ public final class MaskGenerator { /** * Generates a mask using the techniques described in FastCDC. * - * @param effectiveBits The amount of effective bits in the mask (1s) + * @param effectiveBits The amount of effective bits in the mask (1s), positive and not zero * @param seed The seed to use for distribution of the bits * * @return The generated mask */ private static long generateMaskFastCdc(final int effectiveBits, final long seed) { + Validations.requirePositiveNonZero(effectiveBits, "Effective bits"); + // Shuffle a mask with 'effectiveBits' 1s and fill up the rest with '0' // The most significant bit has to be 1 always, hence we only shuffle the rest final List maskBits = new ArrayList<>(); @@ -54,22 +54,24 @@ private static long generateMaskFastCdc(final int effectiveBits, final long seed /** * Generates a mask using the techniques described in NlfiedlerRust. * - * @param bits The amount of effective bits in the mask (1s) + * @param effectiveBits The amount of effective bits in the mask (1s), positive and not zero * * @return The generated mask */ - private static long generateMaskNlfiedlerRust(final int bits) { - return Long.parseLong("1".repeat(bits), 2); + private static long generateMaskNlfiedlerRust(final int effectiveBits) { + Validations.requirePositiveNonZero(effectiveBits, "Effective bits"); + return Long.parseLong("1".repeat(effectiveBits), 2); } /** * Gets the amount of effective bits to use (1s) for the given expected chunk size. * - * @param expectedChunkSize The expected chunk size in bytes + * @param expectedChunkSize The expected chunk size in bytes, positive and not zero * * @return The amount of effective bits to use */ private static int getEffectiveBits(final int expectedChunkSize) { + Validations.requirePositiveNonZero(expectedChunkSize, "Expected chunk size"); return Util.log2(expectedChunkSize); } @@ -93,16 +95,16 @@ private static int getEffectiveBits(final int expectedChunkSize) { /** * Creates a new mask generator. * - * @param maskOption The option describing which technique to use for mask generation - * @param normalizationLevel The normalization level to use - * @param expectedChunkSize The expected chunk size in bytes + * @param maskOption The option describing which technique to use for mask generation, not null + * @param normalizationLevel The normalization level to use, positive + * @param expectedChunkSize The expected chunk size in bytes, positive and not zero * @param seed The seed to use for distributing bits in the mask generation */ public MaskGenerator(final MaskOption maskOption, final int normalizationLevel, final int expectedChunkSize, final long seed) { - this.maskOption = maskOption; - this.normalizationLevel = normalizationLevel; - this.expectedChunkSize = expectedChunkSize; + this.maskOption = Objects.requireNonNull(maskOption); + this.normalizationLevel = Validations.requirePositive(normalizationLevel, "Normalization level"); + this.expectedChunkSize = Validations.requirePositiveNonZero(expectedChunkSize, "Expected chunk size"); this.seed = seed; } diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/NlfiedlerRustChunkerCore.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/NlfiedlerRustChunkerCore.java index d2ff936..80e5a2b 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/NlfiedlerRustChunkerCore.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/NlfiedlerRustChunkerCore.java @@ -1,11 +1,13 @@ package de.zabuza.fastcdc4j.internal.chunking; import de.zabuza.fastcdc4j.external.chunking.IterativeStreamChunkerCore; +import de.zabuza.fastcdc4j.internal.util.Validations; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.util.Objects; /** * Implementation of an iterative stream chunker core that chunks according to a modified FastCDC algorithm (by Nathan @@ -43,11 +45,13 @@ public final class NlfiedlerRustChunkerCore implements IterativeStreamChunkerCor /** * Creates a new core. * - * @param expectedSize The expected size for a single chunk, in bytes - * @param minSize The minimal size for a single chunk, in bytes - * @param maxSize The maximal size for a single chunk, in bytes + * @param expectedSize The expected size for a single chunk, in bytes, must be positive + * @param minSize The minimal size for a single chunk, in bytes, must be positive and less equals expected + * size + * @param maxSize The maximal size for a single chunk, in bytes, must be positive and greater equals expected + * size * @param gear The hash table, also known as {@code gear} used as noise to improve the splitting behavior - * for relatively similar content + * for relatively similar content, must have a length of exactly 256, one hash per byte value * @param maskSmall Mask for the fingerprint that is used for smaller windows, to decrease the likelihood of a * split * @param maskLarge Mask for the fingerprint that is used for bigger windows, to increase the likelihood of a @@ -56,9 +60,16 @@ public final class NlfiedlerRustChunkerCore implements IterativeStreamChunkerCor @SuppressWarnings("ConstructorWithTooManyParameters") public NlfiedlerRustChunkerCore(final int expectedSize, final int minSize, final int maxSize, final long[] gear, final long maskSmall, final long maskLarge) { - this.expectedSize = expectedSize; - this.minSize = minSize; - this.maxSize = maxSize; + Validations.require(minSize <= expectedSize, "Min size must be less equals expected size"); + Validations.require(maxSize >= expectedSize, "Max size must be greater equals expected size"); + Objects.requireNonNull(gear); + //noinspection MagicNumber + Validations.require(gear.length == 256, + "Gear must have a length of 256, one hash per byte value, was: " + gear.length); + + this.expectedSize = Validations.requirePositive(expectedSize, "Expected size"); + this.minSize = Validations.requirePositive(minSize, "Min size"); + this.maxSize = Validations.requirePositive(maxSize, "Max size"); this.gear = gear.clone(); this.maskSmall = maskSmall; this.maskLarge = maskLarge; @@ -66,6 +77,11 @@ public NlfiedlerRustChunkerCore(final int expectedSize, final int minSize, final @Override public byte[] readNextChunk(final InputStream stream, final long size, final long currentOffset) { + Objects.requireNonNull(stream); + Validations.requirePositiveNonZero(size, "Size"); + Validations.requirePositive(currentOffset, "Current offset"); + Validations.require(currentOffset < size, "Current offset must be less than size"); + try (final ByteArrayOutputStream dataBuffer = new ByteArrayOutputStream()) { int normalSize = expectedSize; //noinspection StandardVariableNames diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunk.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunk.java index a593486..9c2e3f1 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunk.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunk.java @@ -2,6 +2,9 @@ import de.zabuza.fastcdc4j.external.chunking.Chunk; import de.zabuza.fastcdc4j.internal.util.Util; +import de.zabuza.fastcdc4j.internal.util.Validations; + +import java.util.Objects; /** * Implementation of a simple chunk, wrapping given data. @@ -35,15 +38,19 @@ public final class SimpleChunk implements Chunk { *

* The {@link #getHexHash()} is cached and will be generated upon construction based on the given hash. * - * @param data The data contained in this chunk - * @param offset The offset of this chunk, with respect to its source data stream + * @param data The data contained in this chunk, not null and not empty + * @param offset The offset of this chunk, with respect to its source data stream, must be positive * @param hash A binary hash representation of the contained data. Using the algorithm specified during - * construction by the {@link de.zabuza.fastcdc4j.external.chunking.Chunker}. + * construction by the {@link de.zabuza.fastcdc4j.external.chunking.Chunker}. Not null and not empty. */ public SimpleChunk(final byte[] data, final long offset, final byte[] hash) { + Objects.requireNonNull(data); + Validations.require(data.length > 0, "Data must not be empty"); + Objects.requireNonNull(hash); + Validations.require(hash.length > 0, "Hash must not be empty"); //noinspection AssignmentOrReturnOfFieldWithMutableType this.data = data; - this.offset = offset; + this.offset = Validations.requirePositive(offset, "Offset"); //noinspection AssignmentOrReturnOfFieldWithMutableType this.hash = hash; hexHash = Util.bytesToHex(hash); diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunkMetadata.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunkMetadata.java index 3744945..977f30e 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunkMetadata.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/chunking/SimpleChunkMetadata.java @@ -1,6 +1,9 @@ package de.zabuza.fastcdc4j.internal.chunking; import de.zabuza.fastcdc4j.external.chunking.ChunkMetadata; +import de.zabuza.fastcdc4j.internal.util.Validations; + +import java.util.Objects; /** * Implementation of a simple chunk metadata, wrapping given data. @@ -30,16 +33,22 @@ public final class SimpleChunkMetadata implements ChunkMetadata { /** * Creates a new simple chunk. * - * @param offset The offset of this chunk, with respect to its source data stream - * @param length The length of this chunk, i.e. the amount of contained data + * @param offset The offset of this chunk, with respect to its source data stream, must be positive + * @param length The length of this chunk, i.e. the amount of contained data, must be positive and not zero * @param hash A binary hash representation of the contained data. Using the algorithm specified during - * construction by the {@link de.zabuza.fastcdc4j.external.chunking.Chunker}. + * construction by the {@link de.zabuza.fastcdc4j.external.chunking.Chunker}. Not null and not + * empty. * @param hexHash A hexadecimal hash representation of the contained data. Using the algorithm specified during - * construction by the {@link de.zabuza.fastcdc4j.external.chunking.Chunker}. + * construction by the {@link de.zabuza.fastcdc4j.external.chunking.Chunker}. Not null and not + * empty. */ public SimpleChunkMetadata(final long offset, final int length, final byte[] hash, final String hexHash) { - this.offset = offset; - this.length = length; + Objects.requireNonNull(hash); + Validations.require(hash.length > 0, "Hash must not be empty"); + Objects.requireNonNull(hexHash); + Validations.require(!hexHash.isEmpty(), "Hex hash must not be empty"); + this.offset = Validations.requirePositive(offset, "Offset"); + this.length = Validations.requirePositiveNonZero(length, "Length"); //noinspection AssignmentOrReturnOfFieldWithMutableType this.hash = hash; this.hexHash = hexHash; diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/FlatIterator.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/FlatIterator.java index 8943622..c709780 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/FlatIterator.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/FlatIterator.java @@ -2,6 +2,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.function.Function; /** @@ -32,13 +33,14 @@ public final class FlatIterator implements Iterator { /** * Creates a new flat iterator that flattens the given iterator on-the-fly. * - * @param outerIterator The source iterator used to generate inner iterators from - * @param provider Function that provides the final inner iterators based on the outer iterators data + * @param outerIterator The source iterator used to generate inner iterators from, not null + * @param provider Function that provides the final inner iterators based on the outer iterators data, not + * null */ public FlatIterator(final Iterator outerIterator, final Function> provider) { - this.outerIterator = outerIterator; - this.provider = provider; + this.outerIterator = Objects.requireNonNull(outerIterator); + this.provider = Objects.requireNonNull(provider); } @Override diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Util.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Util.java index f292633..fce1809 100644 --- a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Util.java +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Util.java @@ -3,6 +3,7 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Objects; /** * Collection of various utility methods of no particular topic. @@ -24,12 +25,13 @@ public enum Util { /** * Creates a hexadecimal representation of the given binary data. * - * @param bytes The binary data to convert + * @param bytes The binary data to convert, not null * * @return Hexadecimal representation */ @SuppressWarnings("MagicNumber") public static String bytesToHex(final byte[] bytes) { + Objects.requireNonNull(bytes); // See https://stackoverflow.com/a/9855338/2411243 //noinspection MultiplyOrDivideByPowerOfTwo final byte[] hexChars = new byte[bytes.length * 2]; @@ -47,12 +49,14 @@ public static String bytesToHex(final byte[] bytes) { /** * Hashes the given data using the given method. * - * @param method The method to use for hashing, must be supported by {@link MessageDigest}. - * @param data The data to hash + * @param method The method to use for hashing, must be supported by {@link MessageDigest}, not null + * @param data The data to hash, not null * * @return The computed hash */ public static byte[] hash(final String method, final byte[] data) { + Objects.requireNonNull(method); + Objects.requireNonNull(data); try { return MessageDigest.getInstance(method) .digest(data); @@ -64,18 +68,13 @@ public static byte[] hash(final String method, final byte[] data) { /** * Computes the logarithm to the base 2 of the given value. * - * @param x The value to compute the log2 of + * @param x The value to compute the log2 of, must be positive and not zero * * @return The log2 of the given value */ public static int log2(final int x) { - if (x >= 0) { - // Safe binary-only conversion without floating points - return Integer.bitCount(Integer.highestOneBit(x) - 1); - } - // Adding epsilon to mitigate floating point errors, see https://stackoverflow.com/a/3305400/2411243 - //noinspection NumericCastThatLosesPrecision - return (int) (Math.log(x) / Math.log(2) + Util.FLOATING_DELTA); - + Validations.requirePositiveNonZero(x, "Value"); + // Safe binary-only conversion without floating points + return Integer.bitCount(Integer.highestOneBit(x) - 1); } } diff --git a/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Validations.java b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Validations.java new file mode 100644 index 0000000..01fcaa9 --- /dev/null +++ b/de.zabuza.fastcdc4j/src/de/zabuza/fastcdc4j/internal/util/Validations.java @@ -0,0 +1,172 @@ +package de.zabuza.fastcdc4j.internal.util; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Utility class providing validation methods. + * + * @author Daniel Tischner {@literal } + */ +public final class Validations { + /** + * Throws {@link IllegalArgumentException} if the given value is not positive, i.e. when it is negative. + * + * @param value The value to test + * @param valueName The name of the value, used to describe it in the exception reason, not null + * + * @return The given value for convenient chaining + */ + @SuppressWarnings("OverloadedMethodsWithSameNumberOfParameters") + public static long requirePositive(final long value, final String valueName) { + Objects.requireNonNull(valueName); + Validations.require(value >= 0, valueName + " must be positive."); + return value; + } + + /** + * Throws {@link IllegalArgumentException} if the given value is not positive or zero, i.e. when it is negative or + * zero. + * + * @param value The value to test + * @param valueName The name of the value, used to describe it in the exception reason, not null + * + * @return The given value for convenient chaining + */ + @SuppressWarnings("OverloadedMethodsWithSameNumberOfParameters") + public static long requirePositiveNonZero(final long value, final String valueName) { + Objects.requireNonNull(valueName); + Validations.require(value > 0, valueName + " must be positive and not zero."); + return value; + } + + /** + * Throws {@link IllegalArgumentException} if the given value is not positive, i.e. when it is negative. + * + * @param value The value to test + * @param valueName The name of the value, used to describe it in the exception reason, not null + * + * @return The given value for convenient chaining + */ + @SuppressWarnings("OverloadedMethodsWithSameNumberOfParameters") + public static int requirePositive(final int value, final String valueName) { + Objects.requireNonNull(valueName); + Validations.require(value >= 0, valueName + " must be positive."); + return value; + } + + /** + * Throws {@link IllegalArgumentException} if the given value is not positive or zero, i.e. when it is negative or + * zero. + * + * @param value The value to test + * @param valueName The name of the value, used to describe it in the exception reason, not null + * + * @return The given value for convenient chaining + */ + @SuppressWarnings("OverloadedMethodsWithSameNumberOfParameters") + public static int requirePositiveNonZero(final int value, final String valueName) { + Objects.requireNonNull(valueName); + Validations.require(value > 0, valueName + " must be positive and not zero."); + return value; + } + + /** + * Throws {@link IllegalArgumentException} if the runnable throws the given expected exception. + * + * @param expectedException The exception for which to throw if it happens, not null + * @param runnable The runnable to execute, not null + * @param message The message to provide in the exception as reason, not null + * @param The type of the exception to expect + */ + public static void requireNotThrow(final Class expectedException, + final Runnable runnable, final String message) { + Objects.requireNonNull(expectedException); + Objects.requireNonNull(runnable); + Objects.requireNonNull(message); + try { + runnable.run(); + } catch (final RuntimeException e) { + if (expectedException.isInstance(e)) { + throw new IllegalArgumentException(message, e); + } + } + } + + /** + * Throws {@link IllegalArgumentException} if the given predicate resolves to true. + * + * @param predicate The predicate to test, not null + * @param object The object to test the predicate against + * @param message The message to provide in the exception, not null + * @param The type of the object to test + * + * @return The given value for convenient chaining + */ + public static T require(final Predicate predicate, final T object, final String message) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(message); + Validations.require(predicate.test(object), message); + return object; + } + + /** + * Throws {@link IllegalArgumentException} if the given predicate resolves to false. + * + * @param predicate The predicate to test + * @param message The message to provide in the exception, not null + */ + @SuppressWarnings("BooleanParameter") + public static void require(final boolean predicate, final String message) { + Objects.requireNonNull(message); + Validations.require(predicate, IllegalArgumentException::new, message); + } + + /** + * Throws the given exception if the predicate resolves to false. + * + * @param predicate The predicate to test, not null + * @param object The object to test the predicate against + * @param exceptionSupplier The supplier to use for getting the exception to throw, not null + * @param message The message to provide in the exception, not null + * @param The type of the object to test + * @param The type of the exception to throw + * + * @return The given value for convenient chaining + */ + public static T require(final Predicate predicate, final T object, + final Function exceptionSupplier, final String message) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(exceptionSupplier); + Objects.requireNonNull(message); + Validations.require(predicate.test(object), exceptionSupplier, message); + return object; + } + + /** + * Throws the given exception if the predicate resolves to false. + * + * @param predicate The predicate to test + * @param exceptionSupplier The supplier to use for getting the exception to throw, not null + * @param message The message to provide in the exception, not null + * @param The type of the exception to throw + */ + @SuppressWarnings({ "BooleanParameter", "WeakerAccess" }) + public static void require(final boolean predicate, + final Function exceptionSupplier, final String message) { + Objects.requireNonNull(exceptionSupplier); + Objects.requireNonNull(message); + if (predicate) { + return; + } + throw exceptionSupplier.apply(message); + } + + /** + * Utility class. No implementation. + */ + private Validations() { + throw new UnsupportedOperationException("Utility class, no implementation"); + } +}