001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with this
004 * work for additional information regarding copyright ownership. The ASF
005 * licenses this file to You under the Apache License, Version 2.0 (the
006 * "License"); you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 * License for the specific language governing permissions and limitations under
015 * the License.
016 */
017package org.apache.commons.compress.harmony.pack200;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.TreeSet;
029
030import org.objectweb.asm.Type;
031
032/**
033 * Pack200 Constant Pool Bands
034 */
035public class CpBands extends BandSet {
036
037    // Don't need to include default attribute names in the constant pool bands
038    private final Set<String> defaultAttributeNames = new HashSet<>();
039
040    private final Set<CPUTF8> cp_Utf8 = new TreeSet<>();
041    private final Set<CPInt> cp_Int = new TreeSet<>();
042    private final Set<CPFloat> cp_Float = new TreeSet<>();
043    private final Set<CPLong> cp_Long = new TreeSet<>();
044    private final Set<CPDouble> cp_Double = new TreeSet<>();
045    private final Set<CPString> cp_String = new TreeSet<>();
046    private final Set<CPClass> cp_Class = new TreeSet<>();
047    private final Set<CPSignature> cp_Signature = new TreeSet<>();
048    private final Set<CPNameAndType> cp_Descr = new TreeSet<>();
049    private final Set<CPMethodOrField> cp_Field = new TreeSet<>();
050    private final Set<CPMethodOrField> cp_Method = new TreeSet<>();
051    private final Set<CPMethodOrField> cp_Imethod = new TreeSet<>();
052
053    private final Map<String, CPUTF8> stringsToCpUtf8 = new HashMap<>();
054    private final Map<String, CPNameAndType> stringsToCpNameAndType = new HashMap<>();
055    private final Map<String, CPClass> stringsToCpClass = new HashMap<>();
056    private final Map<String, CPSignature> stringsToCpSignature = new HashMap<>();
057    private final Map<String, CPMethodOrField> stringsToCpMethod = new HashMap<>();
058    private final Map<String, CPMethodOrField> stringsToCpField = new HashMap<>();
059    private final Map<String, CPMethodOrField> stringsToCpIMethod = new HashMap<>();
060
061    private final Map<Object, CPConstant<?>> objectsToCPConstant = new HashMap<>();
062
063    private final Segment segment;
064
065    public CpBands(final Segment segment, final int effort) {
066        super(effort, segment.getSegmentHeader());
067        this.segment = segment;
068        defaultAttributeNames.add("AnnotationDefault");
069        defaultAttributeNames.add("RuntimeVisibleAnnotations");
070        defaultAttributeNames.add("RuntimeInvisibleAnnotations");
071        defaultAttributeNames.add("RuntimeVisibleParameterAnnotations");
072        defaultAttributeNames.add("RuntimeInvisibleParameterAnnotations");
073        defaultAttributeNames.add("Code");
074        defaultAttributeNames.add("LineNumberTable");
075        defaultAttributeNames.add("LocalVariableTable");
076        defaultAttributeNames.add("LocalVariableTypeTable");
077        defaultAttributeNames.add("ConstantValue");
078        defaultAttributeNames.add("Deprecated");
079        defaultAttributeNames.add("EnclosingMethod");
080        defaultAttributeNames.add("Exceptions");
081        defaultAttributeNames.add("InnerClasses");
082        defaultAttributeNames.add("Signature");
083        defaultAttributeNames.add("SourceFile");
084    }
085
086    private void addCharacters(final List<Character> chars, final char[] charArray) {
087        for (final char element : charArray) {
088            chars.add(Character.valueOf(element));
089        }
090    }
091
092    public void addCPClass(final String className) {
093        getCPClass(className);
094    }
095
096    void addCPUtf8(final String utf8) {
097        getCPUtf8(utf8);
098    }
099
100    private void addIndices() {
101                for (final Set<? extends ConstantPoolEntry> set : Arrays.asList(cp_Utf8, cp_Int, cp_Float, cp_Long, cp_Double,
102                                cp_String, cp_Class, cp_Signature, cp_Descr, cp_Field, cp_Method, cp_Imethod)) {
103                        int j = 0;
104                        for (final ConstantPoolEntry entry : set) {
105                                entry.setIndex(j);
106                                j++;
107                        }
108                }
109                final Map<CPClass, Integer> classNameToIndex = new HashMap<>();
110                cp_Field.forEach(mOrF -> {
111                        final CPClass cpClassName = mOrF.getClassName();
112                        final Integer index = classNameToIndex.get(cpClassName);
113                        if (index == null) {
114                                classNameToIndex.put(cpClassName, Integer.valueOf(1));
115                                mOrF.setIndexInClass(0);
116                        } else {
117                                final int theIndex = index.intValue();
118                                mOrF.setIndexInClass(theIndex);
119                                classNameToIndex.put(cpClassName, Integer.valueOf(theIndex + 1));
120                        }
121                });
122                classNameToIndex.clear();
123                final Map<CPClass, Integer> classNameToConstructorIndex = new HashMap<>();
124                cp_Method.forEach(mOrF -> {
125                        final CPClass cpClassName = mOrF.getClassName();
126                        final Integer index = classNameToIndex.get(cpClassName);
127                        if (index == null) {
128                                classNameToIndex.put(cpClassName, Integer.valueOf(1));
129                                mOrF.setIndexInClass(0);
130                        } else {
131                                final int theIndex = index.intValue();
132                                mOrF.setIndexInClass(theIndex);
133                                classNameToIndex.put(cpClassName, Integer.valueOf(theIndex + 1));
134                        }
135                        if (mOrF.getDesc().getName().equals("<init>")) {
136                                final Integer constructorIndex = classNameToConstructorIndex.get(cpClassName);
137                                if (constructorIndex == null) {
138                                        classNameToConstructorIndex.put(cpClassName, Integer.valueOf(1));
139                                        mOrF.setIndexInClassForConstructor(0);
140                                } else {
141                                        final int theIndex = constructorIndex.intValue();
142                                        mOrF.setIndexInClassForConstructor(theIndex);
143                                        classNameToConstructorIndex.put(cpClassName, Integer.valueOf(theIndex + 1));
144                                }
145                        }
146                });
147        }
148
149    public boolean existsCpClass(final String className) {
150        return stringsToCpClass.containsKey(className);
151    }
152
153    /**
154     * All input classes for the segment have now been read in, so this method is called so that this class can
155     * calculate/complete anything it could not do while classes were being read.
156     */
157    public void finaliseBands() {
158        addCPUtf8("");
159        removeSignaturesFromCpUTF8();
160        addIndices();
161        segmentHeader.setCp_Utf8_count(cp_Utf8.size());
162        segmentHeader.setCp_Int_count(cp_Int.size());
163        segmentHeader.setCp_Float_count(cp_Float.size());
164        segmentHeader.setCp_Long_count(cp_Long.size());
165        segmentHeader.setCp_Double_count(cp_Double.size());
166        segmentHeader.setCp_String_count(cp_String.size());
167        segmentHeader.setCp_Class_count(cp_Class.size());
168        segmentHeader.setCp_Signature_count(cp_Signature.size());
169        segmentHeader.setCp_Descr_count(cp_Descr.size());
170        segmentHeader.setCp_Field_count(cp_Field.size());
171        segmentHeader.setCp_Method_count(cp_Method.size());
172        segmentHeader.setCp_Imethod_count(cp_Imethod.size());
173    }
174
175    public CPConstant<?> getConstant(final Object value) {
176        CPConstant<?> constant = objectsToCPConstant.get(value);
177        if (constant == null) {
178            if (value instanceof Integer) {
179                constant = new CPInt(((Integer) value).intValue());
180                cp_Int.add((CPInt) constant);
181            } else if (value instanceof Long) {
182                constant = new CPLong(((Long) value).longValue());
183                cp_Long.add((CPLong) constant);
184            } else if (value instanceof Float) {
185                constant = new CPFloat(((Float) value).floatValue());
186                cp_Float.add((CPFloat) constant);
187            } else if (value instanceof Double) {
188                constant = new CPDouble(((Double) value).doubleValue());
189                cp_Double.add((CPDouble) constant);
190            } else if (value instanceof String) {
191                constant = new CPString(getCPUtf8((String) value));
192                cp_String.add((CPString) constant);
193            } else if (value instanceof Type) {
194                String className = ((Type) value).getClassName();
195                if (className.endsWith("[]")) {
196                    className = "[L" + className.substring(0, className.length() - 2);
197                    while (className.endsWith("[]")) {
198                        className = "[" + className.substring(0, className.length() - 2);
199                    }
200                    className += ";";
201                }
202                constant = getCPClass(className);
203            }
204            objectsToCPConstant.put(value, constant);
205        }
206        return constant;
207    }
208
209    public CPClass getCPClass(String className) {
210        if (className == null) {
211            return null;
212        }
213        className = className.replace('.', '/');
214        CPClass cpClass = stringsToCpClass.get(className);
215        if (cpClass == null) {
216            final CPUTF8 cpUtf8 = getCPUtf8(className);
217            cpClass = new CPClass(cpUtf8);
218            cp_Class.add(cpClass);
219            stringsToCpClass.put(className, cpClass);
220        }
221        if (cpClass.isInnerClass()) {
222            segment.getClassBands().currentClassReferencesInnerClass(cpClass);
223        }
224        return cpClass;
225    }
226
227    public CPMethodOrField getCPField(final CPClass cpClass, final String name, final String desc) {
228        final String key = cpClass.toString() + ":" + name + ":" + desc;
229        CPMethodOrField cpF = stringsToCpField.get(key);
230        if (cpF == null) {
231            final CPNameAndType nAndT = getCPNameAndType(name, desc);
232            cpF = new CPMethodOrField(cpClass, nAndT);
233            cp_Field.add(cpF);
234            stringsToCpField.put(key, cpF);
235        }
236        return cpF;
237    }
238
239    public CPMethodOrField getCPField(final String owner, final String name, final String desc) {
240        return getCPField(getCPClass(owner), name, desc);
241    }
242
243    public CPMethodOrField getCPIMethod(final CPClass cpClass, final String name, final String desc) {
244        final String key = cpClass.toString() + ":" + name + ":" + desc;
245        CPMethodOrField cpIM = stringsToCpIMethod.get(key);
246        if (cpIM == null) {
247            final CPNameAndType nAndT = getCPNameAndType(name, desc);
248            cpIM = new CPMethodOrField(cpClass, nAndT);
249            cp_Imethod.add(cpIM);
250            stringsToCpIMethod.put(key, cpIM);
251        }
252        return cpIM;
253    }
254
255    public CPMethodOrField getCPIMethod(final String owner, final String name, final String desc) {
256        return getCPIMethod(getCPClass(owner), name, desc);
257    }
258
259    public CPMethodOrField getCPMethod(final CPClass cpClass, final String name, final String desc) {
260        final String key = cpClass.toString() + ":" + name + ":" + desc;
261        CPMethodOrField cpM = stringsToCpMethod.get(key);
262        if (cpM == null) {
263            final CPNameAndType nAndT = getCPNameAndType(name, desc);
264            cpM = new CPMethodOrField(cpClass, nAndT);
265            cp_Method.add(cpM);
266            stringsToCpMethod.put(key, cpM);
267        }
268        return cpM;
269    }
270
271    public CPMethodOrField getCPMethod(final String owner, final String name, final String desc) {
272        return getCPMethod(getCPClass(owner), name, desc);
273    }
274
275        public CPNameAndType getCPNameAndType(final String name, final String signature) {
276        final String descr = name + ":" + signature;
277        CPNameAndType nameAndType = stringsToCpNameAndType.get(descr);
278        if (nameAndType == null) {
279            nameAndType = new CPNameAndType(getCPUtf8(name), getCPSignature(signature));
280            stringsToCpNameAndType.put(descr, nameAndType);
281            cp_Descr.add(nameAndType);
282        }
283        return nameAndType;
284    }
285
286    public CPSignature getCPSignature(final String signature) {
287        if (signature == null) {
288            return null;
289        }
290        CPSignature cpS = stringsToCpSignature.get(signature);
291        if (cpS == null) {
292            final List<CPClass> cpClasses = new ArrayList<>();
293            CPUTF8 signatureUTF8;
294            if (signature.length() > 1 && signature.indexOf('L') != -1) {
295                final List<String> classes = new ArrayList<>();
296                final char[] chars = signature.toCharArray();
297                final StringBuilder signatureString = new StringBuilder();
298                for (int i = 0; i < chars.length; i++) {
299                    signatureString.append(chars[i]);
300                    if (chars[i] == 'L') {
301                        final StringBuilder className = new StringBuilder();
302                        for (int j = i + 1; j < chars.length; j++) {
303                            final char c = chars[j];
304                            if (!Character.isLetter(c) && !Character.isDigit(c) && c != '/' && c != '$'
305                                && c != '_') {
306                                classes.add(className.toString());
307                                i = j - 1;
308                                break;
309                            }
310                            className.append(c);
311                        }
312                    }
313                }
314                removeCpUtf8(signature);
315                for (String className : classes) {
316                    CPClass cpClass = null;
317                    if (className != null) {
318                        className = className.replace('.', '/');
319                        cpClass = stringsToCpClass.get(className);
320                        if (cpClass == null) {
321                            final CPUTF8 cpUtf8 = getCPUtf8(className);
322                            cpClass = new CPClass(cpUtf8);
323                            cp_Class.add(cpClass);
324                            stringsToCpClass.put(className, cpClass);
325                        }
326                    }
327                    cpClasses.add(cpClass);
328                }
329
330                signatureUTF8 = getCPUtf8(signatureString.toString());
331            } else {
332                signatureUTF8 = getCPUtf8(signature);
333            }
334            cpS = new CPSignature(signature, signatureUTF8, cpClasses);
335            cp_Signature.add(cpS);
336            stringsToCpSignature.put(signature, cpS);
337        }
338        return cpS;
339    }
340
341    public CPUTF8 getCPUtf8(final String utf8) {
342        if (utf8 == null) {
343            return null;
344        }
345        CPUTF8 cpUtf8 = stringsToCpUtf8.get(utf8);
346        if (cpUtf8 == null) {
347            cpUtf8 = new CPUTF8(utf8);
348            cp_Utf8.add(cpUtf8);
349            stringsToCpUtf8.put(utf8, cpUtf8);
350        }
351        return cpUtf8;
352    }
353
354    @Override
355    public void pack(final OutputStream out) throws IOException, Pack200Exception {
356        PackingUtils.log("Writing constant pool bands...");
357        writeCpUtf8(out);
358        writeCpInt(out);
359        writeCpFloat(out);
360        writeCpLong(out);
361        writeCpDouble(out);
362        writeCpString(out);
363        writeCpClass(out);
364        writeCpSignature(out);
365        writeCpDescr(out);
366        writeCpMethodOrField(cp_Field, out, "cp_Field");
367        writeCpMethodOrField(cp_Method, out, "cp_Method");
368        writeCpMethodOrField(cp_Imethod, out, "cp_Imethod");
369    }
370
371    private void removeCpUtf8(final String string) {
372        final CPUTF8 utf8 = stringsToCpUtf8.get(string);
373        if (utf8 != null && stringsToCpClass.get(string) == null) { // don't remove if strings are also in cpclass
374            stringsToCpUtf8.remove(string);
375            cp_Utf8.remove(utf8);
376        }
377    }
378
379    private void removeSignaturesFromCpUTF8() {
380        cp_Signature.forEach(signature -> {
381            final String sigStr = signature.getUnderlyingString();
382            final CPUTF8 utf8 = signature.getSignatureForm();
383            final String form = utf8.getUnderlyingString();
384            if (!sigStr.equals(form)) {
385                removeCpUtf8(sigStr);
386            }
387        });
388    }
389
390    private void writeCpClass(final OutputStream out) throws IOException, Pack200Exception {
391        PackingUtils.log("Writing " + cp_Class.size() + " Class entries...");
392        final int[] cpClass = new int[cp_Class.size()];
393        int i = 0;
394        for (final CPClass cpCl : cp_Class) {
395            cpClass[i] = cpCl.getIndexInCpUtf8();
396            i++;
397        }
398        final byte[] encodedBand = encodeBandInt("cpClass", cpClass, Codec.UDELTA5);
399        out.write(encodedBand);
400        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpClass[" + cpClass.length + "]");
401    }
402
403    private void writeCpDescr(final OutputStream out) throws IOException, Pack200Exception {
404        PackingUtils.log("Writing " + cp_Descr.size() + " Descriptor entries...");
405        final int[] cpDescrName = new int[cp_Descr.size()];
406        final int[] cpDescrType = new int[cp_Descr.size()];
407        int i = 0;
408        for (final CPNameAndType nameAndType : cp_Descr) {
409            cpDescrName[i] = nameAndType.getNameIndex();
410            cpDescrType[i] = nameAndType.getTypeIndex();
411            i++;
412        }
413
414        byte[] encodedBand = encodeBandInt("cp_Descr_Name", cpDescrName, Codec.DELTA5);
415        out.write(encodedBand);
416        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Descr_Name[" + cpDescrName.length + "]");
417
418        encodedBand = encodeBandInt("cp_Descr_Type", cpDescrType, Codec.UDELTA5);
419        out.write(encodedBand);
420        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Descr_Type[" + cpDescrType.length + "]");
421    }
422
423    private void writeCpDouble(final OutputStream out) throws IOException, Pack200Exception {
424        PackingUtils.log("Writing " + cp_Double.size() + " Double entries...");
425        final int[] highBits = new int[cp_Double.size()];
426        final int[] loBits = new int[cp_Double.size()];
427        int i = 0;
428        for (final CPDouble dbl : cp_Double) {
429            final long l = Double.doubleToLongBits(dbl.getDouble());
430            highBits[i] = (int) (l >> 32);
431            loBits[i] = (int) l;
432            i++;
433        }
434        byte[] encodedBand = encodeBandInt("cp_Double_hi", highBits, Codec.UDELTA5);
435        out.write(encodedBand);
436        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Double_hi[" + highBits.length + "]");
437
438        encodedBand = encodeBandInt("cp_Double_lo", loBits, Codec.DELTA5);
439        out.write(encodedBand);
440        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Double_lo[" + loBits.length + "]");
441    }
442
443    private void writeCpFloat(final OutputStream out) throws IOException, Pack200Exception {
444        PackingUtils.log("Writing " + cp_Float.size() + " Float entries...");
445        final int[] cpFloat = new int[cp_Float.size()];
446        int i = 0;
447        for (final CPFloat fl : cp_Float) {
448            cpFloat[i] = Float.floatToIntBits(fl.getFloat());
449            i++;
450        }
451        final byte[] encodedBand = encodeBandInt("cp_Float", cpFloat, Codec.UDELTA5);
452        out.write(encodedBand);
453        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Float[" + cpFloat.length + "]");
454    }
455
456    private void writeCpInt(final OutputStream out) throws IOException, Pack200Exception {
457        PackingUtils.log("Writing " + cp_Int.size() + " Integer entries...");
458        final int[] cpInt = new int[cp_Int.size()];
459        int i = 0;
460        for (final CPInt integer : cp_Int) {
461            cpInt[i] = integer.getInt();
462            i++;
463        }
464        final byte[] encodedBand = encodeBandInt("cp_Int", cpInt, Codec.UDELTA5);
465        out.write(encodedBand);
466        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Int[" + cpInt.length + "]");
467    }
468
469    private void writeCpLong(final OutputStream out) throws IOException, Pack200Exception {
470        PackingUtils.log("Writing " + cp_Long.size() + " Long entries...");
471        final int[] highBits = new int[cp_Long.size()];
472        final int[] loBits = new int[cp_Long.size()];
473        int i = 0;
474        for (final CPLong lng : cp_Long) {
475            final long l = lng.getLong();
476            highBits[i] = (int) (l >> 32);
477            loBits[i] = (int) l;
478            i++;
479        }
480        byte[] encodedBand = encodeBandInt("cp_Long_hi", highBits, Codec.UDELTA5);
481        out.write(encodedBand);
482        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Long_hi[" + highBits.length + "]");
483
484        encodedBand = encodeBandInt("cp_Long_lo", loBits, Codec.DELTA5);
485        out.write(encodedBand);
486        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Long_lo[" + loBits.length + "]");
487    }
488
489    private void writeCpMethodOrField(final Set<CPMethodOrField> cp, final OutputStream out, final String name)
490        throws IOException, Pack200Exception {
491        PackingUtils.log("Writing " + cp.size() + " Method and Field entries...");
492        final int[] cp_methodOrField_class = new int[cp.size()];
493        final int[] cp_methodOrField_desc = new int[cp.size()];
494        int i = 0;
495        for (final CPMethodOrField mOrF : cp) {
496            cp_methodOrField_class[i] = mOrF.getClassIndex();
497            cp_methodOrField_desc[i] = mOrF.getDescIndex();
498            i++;
499        }
500        byte[] encodedBand = encodeBandInt(name + "_class", cp_methodOrField_class, Codec.DELTA5);
501        out.write(encodedBand);
502        PackingUtils.log(
503            "Wrote " + encodedBand.length + " bytes from " + name + "_class[" + cp_methodOrField_class.length + "]");
504
505        encodedBand = encodeBandInt(name + "_desc", cp_methodOrField_desc, Codec.UDELTA5);
506        out.write(encodedBand);
507        PackingUtils
508            .log("Wrote " + encodedBand.length + " bytes from " + name + "_desc[" + cp_methodOrField_desc.length + "]");
509    }
510
511    private void writeCpSignature(final OutputStream out) throws IOException, Pack200Exception {
512        PackingUtils.log("Writing " + cp_Signature.size() + " Signature entries...");
513        final int[] cpSignatureForm = new int[cp_Signature.size()];
514        final List<CPClass> classes = new ArrayList<>();
515        int i = 0;
516        for (final CPSignature cpS : cp_Signature) {
517            classes.addAll(cpS.getClasses());
518            cpSignatureForm[i] = cpS.getIndexInCpUtf8();
519            i++;
520        }
521        final int[] cpSignatureClasses = new int[classes.size()];
522        Arrays.setAll(cpSignatureClasses, j -> classes.get(j).getIndex());
523
524        byte[] encodedBand = encodeBandInt("cpSignatureForm", cpSignatureForm, Codec.DELTA5);
525        out.write(encodedBand);
526        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpSignatureForm[" + cpSignatureForm.length + "]");
527
528        encodedBand = encodeBandInt("cpSignatureClasses", cpSignatureClasses, Codec.UDELTA5);
529        out.write(encodedBand);
530        PackingUtils
531            .log("Wrote " + encodedBand.length + " bytes from cpSignatureClasses[" + cpSignatureClasses.length + "]");
532    }
533
534    private void writeCpString(final OutputStream out) throws IOException, Pack200Exception {
535        PackingUtils.log("Writing " + cp_String.size() + " String entries...");
536        final int[] cpString = new int[cp_String.size()];
537        int i = 0;
538        for (final CPString cpStr : cp_String) {
539            cpString[i] = cpStr.getIndexInCpUtf8();
540            i++;
541        }
542        final byte[] encodedBand = encodeBandInt("cpString", cpString, Codec.UDELTA5);
543        out.write(encodedBand);
544        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpString[" + cpString.length + "]");
545    }
546
547    private void writeCpUtf8(final OutputStream out) throws IOException, Pack200Exception {
548        PackingUtils.log("Writing " + cp_Utf8.size() + " UTF8 entries...");
549        final int[] cpUtf8Prefix = new int[cp_Utf8.size() - 2];
550        final int[] cpUtf8Suffix = new int[cp_Utf8.size() - 1];
551        final List<Character> chars = new ArrayList<>();
552        final List<Integer> bigSuffix = new ArrayList<>();
553        final List<Character> bigChars = new ArrayList<>();
554        final Object[] cpUtf8Array = cp_Utf8.toArray();
555        final String first = ((CPUTF8) cpUtf8Array[1]).getUnderlyingString();
556        cpUtf8Suffix[0] = first.length();
557        addCharacters(chars, first.toCharArray());
558        for (int i = 2; i < cpUtf8Array.length; i++) {
559            final char[] previous = ((CPUTF8) cpUtf8Array[i - 1]).getUnderlyingString().toCharArray();
560            String currentStr = ((CPUTF8) cpUtf8Array[i]).getUnderlyingString();
561            final char[] current = currentStr.toCharArray();
562            int prefix = 0;
563            for (int j = 0; j < previous.length; j++) {
564                if (previous[j] != current[j]) {
565                    break;
566                }
567                prefix++;
568            }
569            cpUtf8Prefix[i - 2] = prefix;
570            currentStr = currentStr.substring(prefix);
571            final char[] suffix = currentStr.toCharArray();
572            if (suffix.length > 1000) { // big suffix (1000 is arbitrary - can we
573                // do better?)
574                cpUtf8Suffix[i - 1] = 0;
575                bigSuffix.add(Integer.valueOf(suffix.length));
576                addCharacters(bigChars, suffix);
577            } else {
578                cpUtf8Suffix[i - 1] = suffix.length;
579                addCharacters(chars, suffix);
580            }
581        }
582        final int[] cpUtf8Chars = new int[chars.size()];
583        final int[] cpUtf8BigSuffix = new int[bigSuffix.size()];
584        final int[][] cpUtf8BigChars = new int[bigSuffix.size()][];
585        Arrays.setAll(cpUtf8Chars, i -> chars.get(i).charValue());
586        for (int i = 0; i < cpUtf8BigSuffix.length; i++) {
587            final int numBigChars = bigSuffix.get(i).intValue();
588            cpUtf8BigSuffix[i] = numBigChars;
589            cpUtf8BigChars[i] = new int[numBigChars];
590            Arrays.setAll(cpUtf8BigChars[i], j -> bigChars.remove(0).charValue());
591        }
592
593        byte[] encodedBand = encodeBandInt("cpUtf8Prefix", cpUtf8Prefix, Codec.DELTA5);
594        out.write(encodedBand);
595        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8Prefix[" + cpUtf8Prefix.length + "]");
596
597        encodedBand = encodeBandInt("cpUtf8Suffix", cpUtf8Suffix, Codec.UNSIGNED5);
598        out.write(encodedBand);
599        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8Suffix[" + cpUtf8Suffix.length + "]");
600
601        encodedBand = encodeBandInt("cpUtf8Chars", cpUtf8Chars, Codec.CHAR3);
602        out.write(encodedBand);
603        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8Chars[" + cpUtf8Chars.length + "]");
604
605        encodedBand = encodeBandInt("cpUtf8BigSuffix", cpUtf8BigSuffix, Codec.DELTA5);
606        out.write(encodedBand);
607        PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8BigSuffix[" + cpUtf8BigSuffix.length + "]");
608
609        for (int i = 0; i < cpUtf8BigChars.length; i++) {
610            encodedBand = encodeBandInt("cpUtf8BigChars " + i, cpUtf8BigChars[i], Codec.DELTA5);
611            out.write(encodedBand);
612            PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8BigChars" + i + "["
613                + cpUtf8BigChars[i].length + "]");
614        }
615    }
616
617}