View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.pack200;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.stream.IntStream;
27  
28  /**
29   * Abstract superclass for a set of bands
30   */
31  public abstract class BandSet {
32  
33      /**
34       * Results obtained by trying different Codecs to encode a band
35       */
36      public class BandAnalysisResults {
37  
38          // The number of Codecs tried so far
39          private int numCodecsTried;
40  
41          // The number of bytes saved by using betterCodec instead of the default codec
42          private int saved;
43  
44          // Extra metadata to pass to the segment header (to be appended to the
45          // band_headers band)
46          private int[] extraMetadata;
47  
48          // The results of encoding the band with betterCodec
49          private byte[] encodedBand;
50  
51          // The best Codec found so far, or should be null if the default is the
52          // best so far
53          private Codec betterCodec;
54  
55      }
56  
57      /**
58       * BandData represents information about a band, e.g. largest value etc and is used in the heuristics that calculate whether an alternative Codec could make
59       * the encoded band smaller.
60       */
61      public class BandData {
62  
63          private final int[] band;
64          private int smallest = Integer.MAX_VALUE;
65          private int largest = Integer.MIN_VALUE;
66          private int smallestDelta;
67          private int largestDelta;
68  
69          private int deltaIsAscending;
70          private int smallDeltaCount;
71  
72          private double averageAbsoluteDelta;
73          private double averageAbsoluteValue;
74  
75          private Map<Integer, Integer> distinctValues;
76  
77          /**
78           * Constructs a new instance of BandData. The band is then analysed.
79           *
80           * @param band the band of integers
81           */
82          public BandData(final int[] band) {
83              this.band = band;
84              final Integer one = Integer.valueOf(1);
85              for (int i = 0; i < band.length; i++) {
86                  if (band[i] < smallest) {
87                      smallest = band[i];
88                  }
89                  if (band[i] > largest) {
90                      largest = band[i];
91                  }
92                  if (i != 0) {
93                      final int delta = band[i] - band[i - 1];
94                      if (delta < smallestDelta) {
95                          smallestDelta = delta;
96                      }
97                      if (delta > largestDelta) {
98                          largestDelta = delta;
99                      }
100                     if (delta >= 0) {
101                         deltaIsAscending++;
102                     }
103                     averageAbsoluteDelta += (double) Math.abs(delta) / (double) (band.length - 1);
104                     if (Math.abs(delta) < 256) {
105                         smallDeltaCount++;
106                     }
107                 } else {
108                     smallestDelta = band[0];
109                     largestDelta = band[0];
110                 }
111                 averageAbsoluteValue += (double) Math.abs(band[i]) / (double) band.length;
112                 if (effort > 3) { // do calculations needed to consider population codec
113                     if (distinctValues == null) {
114                         distinctValues = new HashMap<>();
115                     }
116                     final Integer value = Integer.valueOf(band[i]);
117                     Integer count = distinctValues.get(value);
118                     if (count == null) {
119                         count = one;
120                     } else {
121                         count = Integer.valueOf(count.intValue() + 1);
122                     }
123                     distinctValues.put(value, count);
124                 }
125             }
126         }
127 
128         /**
129          * Returns true if any band elements are negative.
130          *
131          * @return true if any band elements are negative.
132          */
133         public boolean anyNegatives() {
134             return smallest < 0;
135         }
136 
137         /**
138          * Returns true if the band deltas are mainly positive (heuristic).
139          *
140          * @return true if the band deltas are mainly positive (heuristic).
141          */
142         public boolean mainlyPositiveDeltas() {
143             // Note: the value below has been tuned - please test carefully if changing it
144             return (float) deltaIsAscending / (float) band.length > 0.95F;
145         }
146 
147         /**
148          * Returns true if the deltas between adjacent band elements are mainly small (heuristic).
149          *
150          * @return true if the deltas between adjacent band elements are mainly small (heuristic).
151          */
152         public boolean mainlySmallDeltas() {
153             // Note: the value below has been tuned - please test carefully if changing it
154             return (float) smallDeltaCount / (float) band.length > 0.7F;
155         }
156 
157         /**
158          * Returns the total number of distinct values found in the band.
159          *
160          * @return the total number of distinct values found in the band.
161          */
162         public int numDistinctValues() {
163             if (distinctValues == null) {
164                 return band.length;
165             }
166             return distinctValues.size();
167         }
168 
169         /**
170          * Returns true if the band is well correlated (i.e. would be suitable for a delta encoding) (heuristic).
171          *
172          * @return true if the band is well correlated (i.e. would be suitable for a delta encoding) (heuristic).
173          */
174         public boolean wellCorrelated() {
175             // Note: the value below has been tuned - please test carefully if changing it
176             return averageAbsoluteDelta * 3.1 < averageAbsoluteValue;
177         }
178 
179     }
180 
181     private static final byte[] EMPTY_BYTE_ARRAY = {};
182 
183     // Minimum size of band for each effort level where we consider alternative codecs
184     // Note: these values have been tuned - please test carefully if changing them
185     private static final int[] effortThresholds = { 0, 0, 1000, 500, 100, 100, 100, 100, 100, 0 };
186 
187     protected final SegmentHeader segmentHeader;
188     final int effort;
189 
190     private long[] canonicalLargest;
191 
192     private long[] canonicalSmallest;
193 
194     /**
195      * Constructs a new BandSet.
196      *
197      * @param effort the packing effort to be used (must be 1-9)
198      * @param header the segment header
199      */
200     public BandSet(final int effort, final SegmentHeader header) {
201         this.effort = effort;
202         this.segmentHeader = header;
203     }
204 
205     private BandAnalysisResults analyseBand(final String name, final int[] band, final BHSDCodec defaultCodec) throws Pack200Exception {
206 
207         final BandAnalysisResults results = new BandAnalysisResults();
208 
209         if (canonicalLargest == null) {
210             canonicalLargest = new long[116];
211             canonicalSmallest = new long[116];
212             for (int i = 1; i < canonicalLargest.length; i++) {
213                 canonicalLargest[i] = CodecEncoding.getCanonicalCodec(i).largest();
214                 canonicalSmallest[i] = CodecEncoding.getCanonicalCodec(i).smallest();
215             }
216         }
217         final BandData bandData = new BandData(band);
218 
219         // Check that there is a reasonable saving to be made
220         final byte[] encoded = defaultCodec.encode(band);
221         results.encodedBand = encoded;
222 
223         // Note: these values have been tuned - please test carefully if changing them
224         if (encoded.length <= band.length + 23 - 2 * effort) { // TODO: tweak
225             return results;
226         }
227 
228         // Check if we can use BYTE1 as that's a 1:1 mapping if we can
229         if (!bandData.anyNegatives() && bandData.largest <= Codec.BYTE1.largest()) {
230             results.encodedBand = Codec.BYTE1.encode(band);
231             results.betterCodec = Codec.BYTE1;
232             return results;
233         }
234 
235         // Consider a population codec (but can't be nested)
236         if (effort > 3 && !name.equals("POPULATION")) {
237             final int numDistinctValues = bandData.numDistinctValues();
238             final float distinctValuesAsProportion = (float) numDistinctValues / (float) band.length;
239 
240             // Note: these values have been tuned - please test carefully if changing them
241             if (numDistinctValues < 100 || distinctValuesAsProportion < 0.02 || effort > 6 && distinctValuesAsProportion < 0.04) { // TODO: tweak
242                 encodeWithPopulationCodec(band, defaultCodec, bandData, results);
243                 if (timeToStop(results)) {
244                     return results;
245                 }
246             }
247         }
248 
249         final List<BHSDCodec[]> codecFamiliesToTry = new ArrayList<>();
250 
251         // See if the deltas are mainly small increments
252         if (bandData.mainlyPositiveDeltas() && bandData.mainlySmallDeltas()) {
253             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs2);
254         }
255 
256         if (bandData.wellCorrelated()) { // Try delta encodings
257             if (bandData.mainlyPositiveDeltas()) {
258                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs1);
259                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs3);
260                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs4);
261                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs5);
262                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs1);
263                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs3);
264                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs4);
265                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs5);
266                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs2);
267             } else {
268                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs1);
269                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs3);
270                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs2);
271                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs4);
272                 codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs5);
273                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs1);
274                 codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs2);
275             }
276         } else if (bandData.anyNegatives()) {
277             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs1);
278             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs2);
279             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs1);
280             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs2);
281             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs3);
282             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs4);
283             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs5);
284         } else {
285             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs1);
286             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs3);
287             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs4);
288             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs5);
289             codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs2);
290             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs1);
291             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs3);
292             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs4);
293             codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs5);
294         }
295         for (final BHSDCodec[] family : codecFamiliesToTry) {
296             tryCodecs(band, defaultCodec, bandData, results, encoded, family);
297             if (timeToStop(results)) {
298                 break;
299             }
300         }
301 
302         return results;
303     }
304 
305     /**
306      * Converts a list of ConstantPoolEntrys to an int[] array of their indices
307      *
308      * @param list conversion source.
309      * @return conversion result.
310      */
311     protected int[] cpEntryListToArray(final List<? extends ConstantPoolEntry> list) {
312         final int[] array = new int[list.size()];
313         for (int i = 0; i < array.length; i++) {
314             array[i] = list.get(i).getIndex();
315             if (array[i] < 0) {
316                 throw new IllegalArgumentException("Index should be > 0");
317             }
318         }
319         return array;
320     }
321 
322     /**
323      * Converts a list of ConstantPoolEntrys or nulls to an int[] array of their indices +1 (or 0 for nulls)
324      *
325      * @param list conversion source.
326      * @return conversion result.
327      */
328     protected int[] cpEntryOrNullListToArray(final List<? extends ConstantPoolEntry> list) {
329         final int[] array = new int[list.size()];
330         for (int j = 0; j < array.length; j++) {
331             final ConstantPoolEntry cpEntry = list.get(j);
332             array[j] = cpEntry == null ? 0 : cpEntry.getIndex() + 1;
333             if (cpEntry != null && cpEntry.getIndex() < 0) {
334                 throw new IllegalArgumentException("Index should be > 0");
335             }
336         }
337         return array;
338     }
339 
340     /**
341      * Encode a band of integers. The default codec may be used, but other Codecs are considered if effort is greater than 1.
342      *
343      * @param name         name of the band (used for debugging)
344      * @param ints         the band
345      * @param defaultCodec the default Codec
346      * @return the encoded band
347      * @throws Pack200Exception TODO
348      */
349     public byte[] encodeBandInt(final String name, final int[] ints, final BHSDCodec defaultCodec) throws Pack200Exception {
350         byte[] encodedBand = null;
351         // Useful for debugging
352 //        if (ints.length > 0) {
353 //            System.out.println("encoding " + name + " " + ints.length);
354 //        }
355         if (effort > 1 && ints.length >= effortThresholds[effort]) {
356             final BandAnalysisResults results = analyseBand(name, ints, defaultCodec);
357             final Codec betterCodec = results.betterCodec;
358             encodedBand = results.encodedBand;
359             if (betterCodec != null) {
360                 if (betterCodec instanceof BHSDCodec) {
361                     final int[] specifierBand = CodecEncoding.getSpecifier(betterCodec, defaultCodec);
362                     int specifier = specifierBand[0];
363                     if (specifierBand.length > 1) {
364                         for (int i = 1; i < specifierBand.length; i++) {
365                             segmentHeader.appendBandCodingSpecifier(specifierBand[i]);
366                         }
367                     }
368                     if (defaultCodec.isSigned()) {
369                         specifier = -1 - specifier;
370                     } else {
371                         specifier += defaultCodec.getL();
372                     }
373                     final byte[] specifierEncoded = defaultCodec.encode(new int[] { specifier });
374                     final byte[] band = new byte[specifierEncoded.length + encodedBand.length];
375                     System.arraycopy(specifierEncoded, 0, band, 0, specifierEncoded.length);
376                     System.arraycopy(encodedBand, 0, band, specifierEncoded.length, encodedBand.length);
377                     return band;
378                 }
379                 if (betterCodec instanceof PopulationCodec) {
380                     IntStream.of(results.extraMetadata).forEach(segmentHeader::appendBandCodingSpecifier);
381                     return encodedBand;
382                 }
383                 if (betterCodec instanceof RunCodec) {
384                     // TODO?
385                 }
386             }
387         }
388 
389         // If we get here then we've decided to use the default codec.
390         if (ints.length > 0) {
391             if (encodedBand == null) {
392                 encodedBand = defaultCodec.encode(ints);
393             }
394             final int first = ints[0];
395             if (defaultCodec.getB() != 1) {
396                 if (defaultCodec.isSigned() && first >= -256 && first <= -1) {
397                     final int specifier = -1 - CodecEncoding.getSpecifierForDefaultCodec(defaultCodec);
398                     final byte[] specifierEncoded = defaultCodec.encode(new int[] { specifier });
399                     final byte[] band = new byte[specifierEncoded.length + encodedBand.length];
400                     System.arraycopy(specifierEncoded, 0, band, 0, specifierEncoded.length);
401                     System.arraycopy(encodedBand, 0, band, specifierEncoded.length, encodedBand.length);
402                     return band;
403                 }
404                 if (!defaultCodec.isSigned() && first >= defaultCodec.getL() && first <= defaultCodec.getL() + 255) {
405                     final int specifier = CodecEncoding.getSpecifierForDefaultCodec(defaultCodec) + defaultCodec.getL();
406                     final byte[] specifierEncoded = defaultCodec.encode(new int[] { specifier });
407                     final byte[] band = new byte[specifierEncoded.length + encodedBand.length];
408                     System.arraycopy(specifierEncoded, 0, band, 0, specifierEncoded.length);
409                     System.arraycopy(encodedBand, 0, band, specifierEncoded.length, encodedBand.length);
410                     return band;
411                 }
412             }
413             return encodedBand;
414         }
415         return EMPTY_BYTE_ARRAY;
416     }
417 
418     /**
419      * Encode a band of longs (values are split into their high and low 32 bits and then encoded as two separate bands
420      *
421      * @param name        name of the band (for debugging purposes)
422      * @param flags       the band
423      * @param loCodec     Codec for the low 32-bits band
424      * @param hiCodec     Codec for the high 32-bits band
425      * @param haveHiFlags ignores the high band if true as all values would be zero
426      * @return the encoded band
427      * @throws Pack200Exception TODO
428      */
429     protected byte[] encodeFlags(final String name, final long[] flags, final BHSDCodec loCodec, final BHSDCodec hiCodec, final boolean haveHiFlags)
430             throws Pack200Exception {
431         if (!haveHiFlags) {
432             final int[] loBits = new int[flags.length];
433             Arrays.setAll(loBits, i -> (int) flags[i]);
434             return encodeBandInt(name, loBits, loCodec);
435         }
436         final int[] hiBits = new int[flags.length];
437         final int[] loBits = new int[flags.length];
438         for (int i = 0; i < flags.length; i++) {
439             final long l = flags[i];
440             hiBits[i] = (int) (l >> 32);
441             loBits[i] = (int) l;
442         }
443         final byte[] hi = encodeBandInt(name, hiBits, hiCodec);
444         final byte[] lo = encodeBandInt(name, loBits, loCodec);
445         final byte[] total = new byte[hi.length + lo.length];
446         System.arraycopy(hi, 0, total, 0, hi.length);
447         System.arraycopy(lo, 0, total, hi.length + 1, lo.length);
448         return total;
449     }
450 
451 // This could be useful if further enhancements are done but is not currently used
452 //
453 //    private void encodeWithRunCodec(String name, int[] band, int index,
454 //            BHSDCodec defaultCodec, BandData bandData,
455 //            BandAnalysisResults results) throws Pack200Exception {
456 //        int[] firstBand = new int[index];
457 //        int[] secondBand = new int[band.length - index];
458 //        System.arraycopy(band, 0, firstBand, 0, index);
459 //        System.arraycopy(band, index, secondBand, 0, secondBand.length);
460 //        BandAnalysisResults firstResults = analyseBand(name + "A", firstBand, defaultCodec);
461 //        BandAnalysisResults secondResults = analyseBand(name + "B", secondBand, defaultCodec);
462 //        int specifier = 117;
463 //        byte[] specifierEncoded = defaultCodec.encode(new int[] {specifier});
464 //        int totalLength = firstResults.encodedBand.length + secondResults.encodedBand.length + specifierEncoded.length + 4; // TODO actual
465 //        if (totalLength < results.encodedBand.length) {
466 //            System.out.println("using run codec");
467 //            results.saved += results.encodedBand.length - totalLength;
468 //            byte[] encodedBand = new byte[specifierEncoded.length + firstResults.encodedBand.length + secondResults.encodedBand.length];
469 //            System.arraycopy(specifierEncoded, 0, encodedBand, 0, specifierEncoded.length);
470 //            System.arraycopy(firstResults.encodedBand, 0, encodedBand, specifierEncoded.length, firstResults.encodedBand.length);
471 //            System.arraycopy(secondResults.encodedBand, 0, encodedBand, specifierEncoded.length + firstResults.encodedBand.length,
472 //              secondResults.encodedBand.length);
473 //            results.encodedBand = encodedBand;
474 //            results.betterCodec = new RunCodec(index, firstResults.betterCodec, secondResults.betterCodec);
475 //        }
476 //    }
477 
478     protected byte[] encodeFlags(final String name, final long[][] flags, final BHSDCodec loCodec, final BHSDCodec hiCodec, final boolean haveHiFlags)
479             throws Pack200Exception {
480         return encodeFlags(name, flatten(flags), loCodec, hiCodec, haveHiFlags);
481     }
482 
483     /**
484      * Encode a single value with the given Codec
485      *
486      * @param value the value to encode
487      * @param codec Codec to use
488      * @return the encoded value
489      * @throws Pack200Exception TODO
490      */
491     public byte[] encodeScalar(final int value, final BHSDCodec codec) throws Pack200Exception {
492         return codec.encode(value);
493     }
494 
495     /**
496      * Encode a band without considering other Codecs
497      *
498      * @param band  the band
499      * @param codec the Codec to use
500      * @return the encoded band
501      * @throws Pack200Exception TODO
502      */
503     public byte[] encodeScalar(final int[] band, final BHSDCodec codec) throws Pack200Exception {
504         return codec.encode(band);
505     }
506 
507     private void encodeWithPopulationCodec(final int[] band, final BHSDCodec defaultCodec, final BandData bandData, final BandAnalysisResults results)
508             throws Pack200Exception {
509         results.numCodecsTried += 3; // quite a bit more effort to try this codec
510         final Map<Integer, Integer> distinctValues = bandData.distinctValues;
511 
512         final List<Integer> favored = new ArrayList<>();
513         distinctValues.forEach((k, v) -> {
514             if (v.intValue() > 2 || distinctValues.size() < 256) { // TODO: tweak
515                 favored.add(k);
516             }
517         });
518 
519         // Sort the favored list with the most commonly occurring first
520         if (distinctValues.size() > 255) {
521             favored.sort((arg0, arg1) -> distinctValues.get(arg1).compareTo(distinctValues.get(arg0)));
522         }
523 
524         final Map<Integer, Integer> favoredToIndex = new HashMap<>();
525         for (int i = 0; i < favored.size(); i++) {
526             favoredToIndex.put(favored.get(i), Integer.valueOf(i));
527         }
528 
529         final IntList unfavoured = new IntList();
530         final int[] tokens = new int[band.length];
531         for (int i = 0; i < band.length; i++) {
532             final Integer favouredIndex = favoredToIndex.get(Integer.valueOf(band[i]));
533             if (favouredIndex == null) {
534                 tokens[i] = 0;
535                 unfavoured.add(band[i]);
536             } else {
537                 tokens[i] = favouredIndex.intValue() + 1;
538             }
539         }
540         favored.add(favored.get(favored.size() - 1)); // repeat last value
541         final int[] favouredBand = integerListToArray(favored);
542         final int[] unfavouredBand = unfavoured.toArray();
543 
544         // Analyse the three bands to get the best codec
545         final BandAnalysisResults favouredResults = analyseBand("POPULATION", favouredBand, defaultCodec);
546         final BandAnalysisResults unfavouredResults = analyseBand("POPULATION", unfavouredBand, defaultCodec);
547 
548         int tdefL = 0;
549         int l = 0;
550         Codec tokenCodec = null;
551         byte[] tokensEncoded;
552         final int k = favored.size() - 1;
553         if (k < 256) {
554             tdefL = 1;
555             tokensEncoded = Codec.BYTE1.encode(tokens);
556         } else {
557             final BandAnalysisResults tokenResults = analyseBand("POPULATION", tokens, defaultCodec);
558             tokenCodec = tokenResults.betterCodec;
559             tokensEncoded = tokenResults.encodedBand;
560             if (tokenCodec == null) {
561                 tokenCodec = defaultCodec;
562             }
563             l = ((BHSDCodec) tokenCodec).getL();
564             final int h = ((BHSDCodec) tokenCodec).getH();
565             final int s = ((BHSDCodec) tokenCodec).getS();
566             final int b = ((BHSDCodec) tokenCodec).getB();
567             final int d = ((BHSDCodec) tokenCodec).isDelta() ? 1 : 0;
568             if (s == 0 && d == 0) {
569                 boolean canUseTDefL = true;
570                 if (b > 1) {
571                     final BHSDCodec oneLowerB = new BHSDCodec(b - 1, h);
572                     if (oneLowerB.largest() >= k) {
573                         canUseTDefL = false;
574                     }
575                 }
576                 if (canUseTDefL) {
577                     switch (l) {
578                     case 4:
579                         tdefL = 1;
580                         break;
581                     case 8:
582                         tdefL = 2;
583                         break;
584                     case 16:
585                         tdefL = 3;
586                         break;
587                     case 32:
588                         tdefL = 4;
589                         break;
590                     case 64:
591                         tdefL = 5;
592                         break;
593                     case 128:
594                         tdefL = 6;
595                         break;
596                     case 192:
597                         tdefL = 7;
598                         break;
599                     case 224:
600                         tdefL = 8;
601                         break;
602                     case 240:
603                         tdefL = 9;
604                         break;
605                     case 248:
606                         tdefL = 10;
607                         break;
608                     case 252:
609                         tdefL = 11;
610                         break;
611                     }
612                 }
613             }
614         }
615 
616         final byte[] favouredEncoded = favouredResults.encodedBand;
617         final byte[] unfavouredEncoded = unfavouredResults.encodedBand;
618         final Codec favouredCodec = favouredResults.betterCodec;
619         final Codec unfavouredCodec = unfavouredResults.betterCodec;
620 
621         int specifier = 141 + (favouredCodec == null ? 1 : 0) + 4 * tdefL + (unfavouredCodec == null ? 2 : 0);
622         final IntList extraBandMetadata = new IntList(3);
623         if (favouredCodec != null) {
624             IntStream.of(CodecEncoding.getSpecifier(favouredCodec, null)).forEach(extraBandMetadata::add);
625         }
626         if (tdefL == 0) {
627             IntStream.of(CodecEncoding.getSpecifier(tokenCodec, null)).forEach(extraBandMetadata::add);
628         }
629         if (unfavouredCodec != null) {
630             IntStream.of(CodecEncoding.getSpecifier(unfavouredCodec, null)).forEach(extraBandMetadata::add);
631         }
632         final int[] extraMetadata = extraBandMetadata.toArray();
633         final byte[] extraMetadataEncoded = Codec.UNSIGNED5.encode(extraMetadata);
634         if (defaultCodec.isSigned()) {
635             specifier = -1 - specifier;
636         } else {
637             specifier += defaultCodec.getL();
638         }
639         final byte[] firstValueEncoded = defaultCodec.encode(new int[] { specifier });
640         final int totalBandLength = firstValueEncoded.length + favouredEncoded.length + tokensEncoded.length + unfavouredEncoded.length;
641 
642         if (totalBandLength + extraMetadataEncoded.length < results.encodedBand.length) {
643             results.saved += results.encodedBand.length - (totalBandLength + extraMetadataEncoded.length);
644             final byte[] encodedBand = new byte[totalBandLength];
645             System.arraycopy(firstValueEncoded, 0, encodedBand, 0, firstValueEncoded.length);
646             System.arraycopy(favouredEncoded, 0, encodedBand, firstValueEncoded.length, favouredEncoded.length);
647             System.arraycopy(tokensEncoded, 0, encodedBand, firstValueEncoded.length + favouredEncoded.length, tokensEncoded.length);
648             System.arraycopy(unfavouredEncoded, 0, encodedBand, firstValueEncoded.length + favouredEncoded.length + tokensEncoded.length,
649                     unfavouredEncoded.length);
650             results.encodedBand = encodedBand;
651             results.extraMetadata = extraMetadata;
652             if (l != 0) {
653                 results.betterCodec = new PopulationCodec(favouredCodec, l, unfavouredCodec);
654             } else {
655                 results.betterCodec = new PopulationCodec(favouredCodec, tokenCodec, unfavouredCodec);
656             }
657         }
658     }
659 
660     /*
661      * Flatten a 2-dimension array into a 1-dimension array
662      */
663     private long[] flatten(final long[][] flags) {
664         int totalSize = 0;
665         for (final long[] flag : flags) {
666             totalSize += flag.length;
667         }
668         final long[] flatArray = new long[totalSize];
669         int index = 0;
670         for (final long[] flag : flags) {
671             for (final long element : flag) {
672                 flatArray[index] = element;
673                 index++;
674             }
675         }
676         return flatArray;
677     }
678 
679     /**
680      * Converts a list of Integers to an int[] array.
681      *
682      * @param integerList conversion source.
683      * @return conversion result.
684      */
685     protected int[] integerListToArray(final List<Integer> integerList) {
686         return integerList.stream().mapToInt(Integer::intValue).toArray();
687     }
688 
689     /**
690      * Converts a list of Longs to an long[] array.
691      *
692      * @param longList conversion source.
693      * @return conversion result.
694      */
695     protected long[] longListToArray(final List<Long> longList) {
696         return longList.stream().mapToLong(Long::longValue).toArray();
697     }
698 
699     /**
700      * Write the packed set of bands to the given output stream
701      *
702      * @param out TODO
703      * @throws IOException      If an I/O error occurs.
704      * @throws Pack200Exception TODO
705      */
706     public abstract void pack(OutputStream out) throws IOException, Pack200Exception;
707 
708     private boolean timeToStop(final BandAnalysisResults results) {
709         // if tried more than effort number of codecs for this band then return
710         // Note: these values have been tuned - please test carefully if changing them
711         if (effort > 6) {
712             return results.numCodecsTried >= effort * 2;
713         }
714         return results.numCodecsTried >= effort;
715         // May want to also check how much we've saved if performance needs improving, e.g. saved more than effort*2 %
716         // || (float) results.saved/(float) results.encodedBand.length > (float) effort * 2/100;
717     }
718 
719     private void tryCodecs(final int[] band, final BHSDCodec defaultCodec, final BandData bandData, final BandAnalysisResults results, final byte[] encoded,
720             final BHSDCodec[] potentialCodecs) throws Pack200Exception {
721         for (final BHSDCodec potential : potentialCodecs) {
722             if (potential.equals(defaultCodec)) {
723                 return; // don't try codecs with greater cardinality in the same 'family' as the default codec as there
724                         // won't be any savings
725             }
726             if (potential.isDelta()) {
727                 if (potential.largest() >= bandData.largestDelta && potential.smallest() <= bandData.smallestDelta && potential.largest() >= bandData.largest
728                         && potential.smallest() <= bandData.smallest) {
729                     // TODO: can represent some negative deltas with overflow
730                     final byte[] encoded2 = potential.encode(band);
731                     results.numCodecsTried++;
732                     final byte[] specifierEncoded = defaultCodec.encode(CodecEncoding.getSpecifier(potential, null));
733                     final int saved = encoded.length - encoded2.length - specifierEncoded.length;
734                     if (saved > results.saved) {
735                         results.betterCodec = potential;
736                         results.encodedBand = encoded2;
737                         results.saved = saved;
738                     }
739                 }
740             } else if (potential.largest() >= bandData.largest && potential.smallest() <= bandData.smallest) {
741                 final byte[] encoded2 = potential.encode(band);
742                 results.numCodecsTried++;
743                 final byte[] specifierEncoded = defaultCodec.encode(CodecEncoding.getSpecifier(potential, null));
744                 final int saved = encoded.length - encoded2.length - specifierEncoded.length;
745                 if (saved > results.saved) {
746                     results.betterCodec = potential;
747                     results.encodedBand = encoded2;
748                     results.saved = saved;
749                 }
750             }
751             if (timeToStop(results)) {
752                 return;
753             }
754         }
755     }
756 
757 }