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