View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.compress.harmony.pack200;
21  
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeSet;
32  import java.util.function.BiFunction;
33  
34  import org.objectweb.asm.Type;
35  
36  /**
37   * Pack200 Constant Pool Bands
38   */
39  public class CpBands extends BandSet {
40  
41      // Don't need to include default attribute names in the constant pool bands
42      private final Set<String> defaultAttributeNames = new HashSet<>();
43  
44      private final Set<CPUTF8> cp_Utf8 = new TreeSet<>();
45      private final Set<CPInt> cp_Int = new TreeSet<>();
46      private final Set<CPFloat> cp_Float = new TreeSet<>();
47      private final Set<CPLong> cp_Long = new TreeSet<>();
48      private final Set<CPDouble> cp_Double = new TreeSet<>();
49      private final Set<CPString> cp_String = new TreeSet<>();
50      private final Set<CPClass> cp_Class = new TreeSet<>();
51      private final Set<CPSignature> cp_Signature = new TreeSet<>();
52      private final Set<CPNameAndType> cp_Descr = new TreeSet<>();
53      private final Set<CPMethodOrField> cp_Field = new TreeSet<>();
54      private final Set<CPMethodOrField> cp_Method = new TreeSet<>();
55      private final Set<CPMethodOrField> cp_Imethod = new TreeSet<>();
56  
57      private final Map<String, CPUTF8> stringsToCpUtf8 = new HashMap<>();
58      private final Map<String, CPNameAndType> stringsToCpNameAndType = new HashMap<>();
59      private final Map<String, CPClass> stringsToCpClass = new HashMap<>();
60      private final Map<String, CPSignature> stringsToCpSignature = new HashMap<>();
61      private final Map<String, CPMethodOrField> stringsToCpMethod = new HashMap<>();
62      private final Map<String, CPMethodOrField> stringsToCpField = new HashMap<>();
63      private final Map<String, CPMethodOrField> stringsToCpIMethod = new HashMap<>();
64  
65      private final Map<Object, CPConstant<?>> objectsToCPConstant = new HashMap<>();
66  
67      private final Segment segment;
68  
69      public CpBands(final Segment segment, final int effort) {
70          super(effort, segment.getSegmentHeader());
71          this.segment = segment;
72          defaultAttributeNames.add("AnnotationDefault");
73          defaultAttributeNames.add("RuntimeVisibleAnnotations");
74          defaultAttributeNames.add("RuntimeInvisibleAnnotations");
75          defaultAttributeNames.add("RuntimeVisibleParameterAnnotations");
76          defaultAttributeNames.add("RuntimeInvisibleParameterAnnotations");
77          defaultAttributeNames.add("Code");
78          defaultAttributeNames.add("LineNumberTable");
79          defaultAttributeNames.add("LocalVariableTable");
80          defaultAttributeNames.add("LocalVariableTypeTable");
81          defaultAttributeNames.add("ConstantValue");
82          defaultAttributeNames.add("Deprecated");
83          defaultAttributeNames.add("EnclosingMethod");
84          defaultAttributeNames.add("Exceptions");
85          defaultAttributeNames.add("InnerClasses");
86          defaultAttributeNames.add("Signature");
87          defaultAttributeNames.add("SourceFile");
88      }
89  
90      private void addCharacters(final List<Character> chars, final char[] charArray) {
91          for (final char element : charArray) {
92              chars.add(Character.valueOf(element));
93          }
94      }
95  
96      public void addCPClass(final String className) {
97          getCPClass(className);
98      }
99  
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 }