001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.compressors.lz77support;
020
021/**
022 * Parameters of the {@link LZ77Compressor compressor}.
023 */
024public final class Parameters {
025    /**
026     * The hard-coded absolute minimal length of a back-reference.
027     */
028    public static final int TRUE_MIN_BACK_REFERENCE_LENGTH = LZ77Compressor.NUMBER_OF_BYTES_IN_HASH;
029
030    /**
031     * Initializes the builder for the compressor's parameters with a
032     * <code>minBackReferenceLength</code> of 3 and <code>max*Length</code>
033     * equal to <code>windowSize - 1</code>.
034     *
035     * <p>It is recommended to not use this method directly but rather
036     * tune a pre-configured builder created by a format specific
037     * factory like {@link
038     * org.apache.commons.compress.compressors.snappy.SnappyCompressorOutputStream#createParameterBuilder}.</p>
039     *
040     * @param windowSize the size of the sliding window - this
041     * determines the maximum offset a back-reference can take. Must
042     * be a power of two.
043     * @throws IllegalArgumentException if windowSize is not a power of two.
044     * @return a builder configured for the given window size
045     */
046    public static Builder builder(int windowSize) {
047        return new Builder(windowSize);
048    }
049
050    /**
051     * Builder for {@link Parameters} instances.
052     */
053    public static class Builder {
054        private final int windowSize;
055        private int minBackReferenceLength, maxBackReferenceLength, maxOffset, maxLiteralLength;
056        private Integer niceBackReferenceLength, maxCandidates, lazyThreshold;
057        private Boolean lazyMatches;
058
059        private Builder(int windowSize) {
060            if (windowSize < 2 || !isPowerOfTwo(windowSize)) {
061                throw new IllegalArgumentException("windowSize must be a power of two");
062            }
063            this.windowSize = windowSize;
064            minBackReferenceLength = TRUE_MIN_BACK_REFERENCE_LENGTH;
065            maxBackReferenceLength = windowSize - 1;
066            maxOffset = windowSize - 1;
067            maxLiteralLength = windowSize;
068        }
069
070        /**
071         * Sets the mininal length of a back-reference.
072         *
073         * <p>Ensures <code>maxBackReferenceLength</code> is not
074         * smaller than <code>minBackReferenceLength</code>.
075         *
076         * <p>It is recommended to not use this method directly but
077         * rather tune a pre-configured builder created by a format
078         * specific factory like {@link
079         * org.apache.commons.compress.compressors.snappy.SnappyCompressorOutputStream#createParameterBuilder}.</p>
080         *
081         * @param minBackReferenceLength the minimal length of a back-reference found. A
082         * true minimum of 3 is hard-coded inside of this implemention
083         * but bigger lengths can be configured.
084         * @throws IllegalArgumentException if <code>windowSize</code>
085         * is smaller than <code>minBackReferenceLength</code>.
086         * @return the builder
087         */
088        public Builder withMinBackReferenceLength(int minBackReferenceLength) {
089            this.minBackReferenceLength = Math.max(TRUE_MIN_BACK_REFERENCE_LENGTH, minBackReferenceLength);
090            if (windowSize < this.minBackReferenceLength) {
091                throw new IllegalArgumentException("minBackReferenceLength can't be bigger than windowSize");
092            }
093            if (maxBackReferenceLength < this.minBackReferenceLength) {
094                maxBackReferenceLength = this.minBackReferenceLength;
095            }
096            return this;
097        }
098
099        /**
100         * Sets the maximal length of a back-reference.
101         *
102         * <p>It is recommended to not use this method directly but
103         * rather tune a pre-configured builder created by a format
104         * specific factory like {@link
105         * org.apache.commons.compress.compressors.snappy.SnappyCompressorOutputStream#createParameterBuilder}.</p>
106         *
107         * @param maxBackReferenceLength maximal length of a
108         * back-reference found. A value smaller than
109         * <code>minBackReferenceLength</code> is interpreted as
110         * <code>minBackReferenceLength</code>. <code>maxBackReferenceLength</code>
111         * is capped at <code>windowSize - 1</code>.
112         * @return the builder
113         */
114        public Builder withMaxBackReferenceLength(int maxBackReferenceLength) {
115            this.maxBackReferenceLength = maxBackReferenceLength < minBackReferenceLength ? minBackReferenceLength
116                : Math.min(maxBackReferenceLength, windowSize - 1);
117            return this;
118        }
119
120        /**
121         * Sets the maximal offset of a back-reference.
122         *
123         * <p>It is recommended to not use this method directly but
124         * rather tune a pre-configured builder created by a format
125         * specific factory like {@link
126         * org.apache.commons.compress.compressors.snappy.SnappyCompressorOutputStream#createParameterBuilder}.</p>
127         *
128         * @param maxOffset maximal offset of a back-reference. A
129         * non-positive value as well as values bigger than
130         * <code>windowSize - 1</code> are interpreted as <code>windowSize
131         * - 1</code>.
132         * @return the builder
133         */
134        public Builder withMaxOffset(int maxOffset) {
135            this.maxOffset = maxOffset < 1 ? windowSize - 1 : Math.min(maxOffset, windowSize - 1);
136            return this;
137        }
138
139        /**
140         * Sets the maximal length of a literal block.
141         *
142         * <p>It is recommended to not use this method directly but
143         * rather tune a pre-configured builder created by a format
144         * specific factory like {@link
145         * org.apache.commons.compress.compressors.snappy.SnappyCompressorOutputStream#createParameterBuilder}.</p>
146         *
147         * @param maxLiteralLength maximal length of a literal
148         * block. Negative numbers and 0 as well as values bigger than
149         * <code>windowSize</code> are interpreted as
150         * <code>windowSize</code>.
151         * @return the builder
152         */
153        public Builder withMaxLiteralLength(int maxLiteralLength) {
154            this.maxLiteralLength = maxLiteralLength < 1 ? windowSize
155                : Math.min(maxLiteralLength, windowSize);
156            return this;
157        }
158
159        /**
160         * Sets the "nice length" of a back-reference.
161         *
162         * <p>When a back-references if this size has been found, stop searching for longer back-references.</p>
163         *
164         * <p>This settings can be used to tune the tradeoff between compression speed and compression ratio.</p>
165         * @param niceLen the "nice length" of a back-reference
166         * @return the builder
167         */
168        public Builder withNiceBackReferenceLength(int niceLen) {
169            niceBackReferenceLength = niceLen;
170            return this;
171        }
172
173        /**
174         * Sets the maximum number of back-reference candidates that should be consulted.
175         *
176         * <p>This settings can be used to tune the tradeoff between compression speed and compression ratio.</p>
177         * @param maxCandidates maximum number of back-reference candidates
178         * @return the builder
179         */
180        public Builder withMaxNumberOfCandidates(int maxCandidates) {
181            this.maxCandidates = maxCandidates;
182            return this;
183        }
184
185        /**
186         * Sets whether lazy matching should be performed.
187         *
188         * <p>Lazy matching means that after a back-reference for a certain position has been found the compressor will
189         * try to find a longer match for the next position.</p>
190         *
191         * <p>Lazy matching is enabled by default and disabled when tuning for speed.</p>
192         * @param lazy whether lazy matching should be performed
193         * @return the builder
194         */
195        public Builder withLazyMatching(boolean lazy) {
196            lazyMatches = lazy;
197            return this;
198        }
199
200        /**
201         * Sets the threshold for lazy matching.
202         *
203         * <p>Even if lazy matching is enabled it will not be performed if the length of the back-reference found for
204         * the current position is longer than this value.</p>
205         * @param threshold the threshold for lazy matching
206         * @return the builder
207         */
208        public Builder withLazyThreshold(int threshold) {
209            lazyThreshold = threshold;
210            return this;
211        }
212
213        /**
214         * Changes the default setting for "nice back-reference length" and "maximum number of candidates" for improved
215         * compression speed at the cost of compression ratio.
216         *
217         * <p>Use this method after configuring "maximum back-reference length".</p>
218         * @return the builder
219         */
220        public Builder tunedForSpeed() {
221            niceBackReferenceLength = Math.max(minBackReferenceLength, maxBackReferenceLength / 8);
222            maxCandidates = Math.max(32, windowSize / 1024);
223            lazyMatches = false;
224            lazyThreshold = minBackReferenceLength;
225            return this;
226        }
227
228        /**
229         * Changes the default setting for "nice back-reference length" and "maximum number of candidates" for improved
230         * compression ratio at the cost of compression speed.
231         *
232         * <p>Use this method after configuring "maximum back-reference length".</p>
233         * @return the builder
234         */
235        public Builder tunedForCompressionRatio() {
236            niceBackReferenceLength = lazyThreshold = maxBackReferenceLength;
237            maxCandidates = Math.max(32, windowSize / 16);
238            lazyMatches = true;
239            return this;
240        }
241
242        /**
243         * Creates the {@link Parameters} instance.
244         * @return the configured {@link Parameters} instance.
245         */
246        public Parameters build() {
247            // default settings tuned for a compromise of good compression and acceptable speed
248            int niceLen = niceBackReferenceLength != null ? niceBackReferenceLength
249                : Math.max(minBackReferenceLength, maxBackReferenceLength / 2);
250            int candidates = maxCandidates != null ? maxCandidates : Math.max(256, windowSize / 128);
251            boolean lazy = lazyMatches == null || lazyMatches;
252            int threshold = lazy ? (lazyThreshold != null ? lazyThreshold : niceLen) : minBackReferenceLength;
253
254            return new Parameters(windowSize, minBackReferenceLength, maxBackReferenceLength,
255                maxOffset, maxLiteralLength, niceLen, candidates, lazy, threshold);
256        }
257    }
258
259    private final int windowSize, minBackReferenceLength, maxBackReferenceLength, maxOffset, maxLiteralLength,
260        niceBackReferenceLength, maxCandidates, lazyThreshold;
261    private final boolean lazyMatching;
262
263    private Parameters(int windowSize, int minBackReferenceLength, int maxBackReferenceLength, int maxOffset,
264            int maxLiteralLength, int niceBackReferenceLength, int maxCandidates, boolean lazyMatching,
265            int lazyThreshold) {
266        this.windowSize = windowSize;
267        this.minBackReferenceLength = minBackReferenceLength;
268        this.maxBackReferenceLength = maxBackReferenceLength;
269        this.maxOffset = maxOffset;
270        this.maxLiteralLength = maxLiteralLength;
271        this.niceBackReferenceLength = niceBackReferenceLength;
272        this.maxCandidates = maxCandidates;
273        this.lazyMatching = lazyMatching;
274        this.lazyThreshold = lazyThreshold;
275    }
276
277    /**
278     * Gets the size of the sliding window - this determines the
279     * maximum offset a back-reference can take.
280     * @return the size of the sliding window
281     */
282    public int getWindowSize() {
283        return windowSize;
284    }
285    /**
286     * Gets the minimal length of a back-reference found.
287     * @return the minimal length of a back-reference found
288     */
289    public int getMinBackReferenceLength() {
290        return minBackReferenceLength;
291    }
292    /**
293     * Gets the maximal length of a back-reference found.
294     * @return the maximal length of a back-reference found
295     */
296    public int getMaxBackReferenceLength() {
297        return maxBackReferenceLength;
298    }
299    /**
300     * Gets the maximal offset of a back-reference found.
301     * @return the maximal offset of a back-reference found
302     */
303    public int getMaxOffset() {
304        return maxOffset;
305    }
306    /**
307     * Gets the maximal length of a literal block.
308     * @return the maximal length of a literal block
309     */
310    public int getMaxLiteralLength() {
311        return maxLiteralLength;
312    }
313
314    /**
315     * Gets the length of a back-reference that is considered nice enough to stop searching for longer ones.
316     * @return the length of a back-reference that is considered nice enough to stop searching
317     */
318    public int getNiceBackReferenceLength() {
319        return niceBackReferenceLength;
320    }
321
322    /**
323     * Gets the maximum number of back-reference candidates to consider.
324     * @return the maximum number of back-reference candidates to consider
325     */
326    public int getMaxCandidates() {
327        return maxCandidates;
328    }
329
330    /**
331     * Gets whether to perform lazy matching.
332     * @return whether to perform lazy matching
333     */
334    public boolean getLazyMatching() {
335        return lazyMatching;
336    }
337
338    /**
339     * Gets the threshold for lazy matching.
340     * @return the threshold for lazy matching
341     */
342    public int getLazyMatchingThreshold() {
343        return lazyThreshold;
344    }
345
346    private static final boolean isPowerOfTwo(int x) {
347        // pre-condition: x > 0
348        return (x & (x - 1)) == 0;
349    }
350}