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 this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to You under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * 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, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations under
15   * 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.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.TreeSet;
29  
30  import org.objectweb.asm.Type;
31  
32  /**
33   * Pack200 Constant Pool Bands
34   */
35  public class CpBands extends BandSet {
36  
37      // Don't need to include default attribute names in the constant pool bands
38      private final Set<String> defaultAttributeNames = new HashSet<>();
39  
40      private final Set<CPUTF8> cp_Utf8 = new TreeSet<>();
41      private final Set<CPInt> cp_Int = new TreeSet<>();
42      private final Set<CPFloat> cp_Float = new TreeSet<>();
43      private final Set<CPLong> cp_Long = new TreeSet<>();
44      private final Set<CPDouble> cp_Double = new TreeSet<>();
45      private final Set<CPString> cp_String = new TreeSet<>();
46      private final Set<CPClass> cp_Class = new TreeSet<>();
47      private final Set<CPSignature> cp_Signature = new TreeSet<>();
48      private final Set<CPNameAndType> cp_Descr = new TreeSet<>();
49      private final Set<CPMethodOrField> cp_Field = new TreeSet<>();
50      private final Set<CPMethodOrField> cp_Method = new TreeSet<>();
51      private final Set<CPMethodOrField> cp_Imethod = new TreeSet<>();
52  
53      private final Map<String, CPUTF8> stringsToCpUtf8 = new HashMap<>();
54      private final Map<String, CPNameAndType> stringsToCpNameAndType = new HashMap<>();
55      private final Map<String, CPClass> stringsToCpClass = new HashMap<>();
56      private final Map<String, CPSignature> stringsToCpSignature = new HashMap<>();
57      private final Map<String, CPMethodOrField> stringsToCpMethod = new HashMap<>();
58      private final Map<String, CPMethodOrField> stringsToCpField = new HashMap<>();
59      private final Map<String, CPMethodOrField> stringsToCpIMethod = new HashMap<>();
60  
61      private final Map<Object, CPConstant<?>> objectsToCPConstant = new HashMap<>();
62  
63      private final Segment segment;
64  
65      public CpBands(final Segment segment, final int effort) {
66          super(effort, segment.getSegmentHeader());
67          this.segment = segment;
68          defaultAttributeNames.add("AnnotationDefault");
69          defaultAttributeNames.add("RuntimeVisibleAnnotations");
70          defaultAttributeNames.add("RuntimeInvisibleAnnotations");
71          defaultAttributeNames.add("RuntimeVisibleParameterAnnotations");
72          defaultAttributeNames.add("RuntimeInvisibleParameterAnnotations");
73          defaultAttributeNames.add("Code");
74          defaultAttributeNames.add("LineNumberTable");
75          defaultAttributeNames.add("LocalVariableTable");
76          defaultAttributeNames.add("LocalVariableTypeTable");
77          defaultAttributeNames.add("ConstantValue");
78          defaultAttributeNames.add("Deprecated");
79          defaultAttributeNames.add("EnclosingMethod");
80          defaultAttributeNames.add("Exceptions");
81          defaultAttributeNames.add("InnerClasses");
82          defaultAttributeNames.add("Signature");
83          defaultAttributeNames.add("SourceFile");
84      }
85  
86      private void addCharacters(final List<Character> chars, final char[] charArray) {
87          for (final char element : charArray) {
88              chars.add(Character.valueOf(element));
89          }
90      }
91  
92      public void addCPClass(final String className) {
93          getCPClass(className);
94      }
95  
96      void addCPUtf8(final String utf8) {
97          getCPUtf8(utf8);
98      }
99  
100     private void addIndices() {
101         for (final Set<? extends ConstantPoolEntry> set : Arrays.asList(cp_Utf8, cp_Int, cp_Float, cp_Long, cp_Double, cp_String, cp_Class, cp_Signature,
102                 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 calculate/complete anything it could not do
155      * 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 != '$' && c != '_') {
305                                 classes.add(className.toString());
306                                 i = j - 1;
307                                 break;
308                             }
309                             className.append(c);
310                         }
311                     }
312                 }
313                 removeCpUtf8(signature);
314                 for (String className : classes) {
315                     CPClass cpClass = null;
316                     if (className != null) {
317                         className = className.replace('.', '/');
318                         cpClass = stringsToCpClass.get(className);
319                         if (cpClass == null) {
320                             final CPUTF8 cpUtf8 = getCPUtf8(className);
321                             cpClass = new CPClass(cpUtf8);
322                             cp_Class.add(cpClass);
323                             stringsToCpClass.put(className, cpClass);
324                         }
325                     }
326                     cpClasses.add(cpClass);
327                 }
328 
329                 signatureUTF8 = getCPUtf8(signatureString.toString());
330             } else {
331                 signatureUTF8 = getCPUtf8(signature);
332             }
333             cpS = new CPSignature(signature, signatureUTF8, cpClasses);
334             cp_Signature.add(cpS);
335             stringsToCpSignature.put(signature, cpS);
336         }
337         return cpS;
338     }
339 
340     public CPUTF8 getCPUtf8(final String utf8) {
341         if (utf8 == null) {
342             return null;
343         }
344         CPUTF8 cpUtf8 = stringsToCpUtf8.get(utf8);
345         if (cpUtf8 == null) {
346             cpUtf8 = new CPUTF8(utf8);
347             cp_Utf8.add(cpUtf8);
348             stringsToCpUtf8.put(utf8, cpUtf8);
349         }
350         return cpUtf8;
351     }
352 
353     @Override
354     public void pack(final OutputStream out) throws IOException, Pack200Exception {
355         PackingUtils.log("Writing constant pool bands...");
356         writeCpUtf8(out);
357         writeCpInt(out);
358         writeCpFloat(out);
359         writeCpLong(out);
360         writeCpDouble(out);
361         writeCpString(out);
362         writeCpClass(out);
363         writeCpSignature(out);
364         writeCpDescr(out);
365         writeCpMethodOrField(cp_Field, out, "cp_Field");
366         writeCpMethodOrField(cp_Method, out, "cp_Method");
367         writeCpMethodOrField(cp_Imethod, out, "cp_Imethod");
368     }
369 
370     private void removeCpUtf8(final String string) {
371         final CPUTF8 utf8 = stringsToCpUtf8.get(string);
372         if (utf8 != null && stringsToCpClass.get(string) == null) { // don't remove if strings are also in cpclass
373             stringsToCpUtf8.remove(string);
374             cp_Utf8.remove(utf8);
375         }
376     }
377 
378     private void removeSignaturesFromCpUTF8() {
379         cp_Signature.forEach(signature -> {
380             final String sigStr = signature.getUnderlyingString();
381             final CPUTF8 utf8 = signature.getSignatureForm();
382             final String form = utf8.getUnderlyingString();
383             if (!sigStr.equals(form)) {
384                 removeCpUtf8(sigStr);
385             }
386         });
387     }
388 
389     private void writeCpClass(final OutputStream out) throws IOException, Pack200Exception {
390         PackingUtils.log("Writing " + cp_Class.size() + " Class entries...");
391         final int[] cpClass = new int[cp_Class.size()];
392         int i = 0;
393         for (final CPClass cpCl : cp_Class) {
394             cpClass[i] = cpCl.getIndexInCpUtf8();
395             i++;
396         }
397         final byte[] encodedBand = encodeBandInt("cpClass", cpClass, Codec.UDELTA5);
398         out.write(encodedBand);
399         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpClass[" + cpClass.length + "]");
400     }
401 
402     private void writeCpDescr(final OutputStream out) throws IOException, Pack200Exception {
403         PackingUtils.log("Writing " + cp_Descr.size() + " Descriptor entries...");
404         final int[] cpDescrName = new int[cp_Descr.size()];
405         final int[] cpDescrType = new int[cp_Descr.size()];
406         int i = 0;
407         for (final CPNameAndType nameAndType : cp_Descr) {
408             cpDescrName[i] = nameAndType.getNameIndex();
409             cpDescrType[i] = nameAndType.getTypeIndex();
410             i++;
411         }
412 
413         byte[] encodedBand = encodeBandInt("cp_Descr_Name", cpDescrName, Codec.DELTA5);
414         out.write(encodedBand);
415         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Descr_Name[" + cpDescrName.length + "]");
416 
417         encodedBand = encodeBandInt("cp_Descr_Type", cpDescrType, Codec.UDELTA5);
418         out.write(encodedBand);
419         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Descr_Type[" + cpDescrType.length + "]");
420     }
421 
422     private void writeCpDouble(final OutputStream out) throws IOException, Pack200Exception {
423         PackingUtils.log("Writing " + cp_Double.size() + " Double entries...");
424         final int[] highBits = new int[cp_Double.size()];
425         final int[] loBits = new int[cp_Double.size()];
426         int i = 0;
427         for (final CPDouble dbl : cp_Double) {
428             final long l = Double.doubleToLongBits(dbl.getDouble());
429             highBits[i] = (int) (l >> 32);
430             loBits[i] = (int) l;
431             i++;
432         }
433         byte[] encodedBand = encodeBandInt("cp_Double_hi", highBits, Codec.UDELTA5);
434         out.write(encodedBand);
435         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Double_hi[" + highBits.length + "]");
436 
437         encodedBand = encodeBandInt("cp_Double_lo", loBits, Codec.DELTA5);
438         out.write(encodedBand);
439         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Double_lo[" + loBits.length + "]");
440     }
441 
442     private void writeCpFloat(final OutputStream out) throws IOException, Pack200Exception {
443         PackingUtils.log("Writing " + cp_Float.size() + " Float entries...");
444         final int[] cpFloat = new int[cp_Float.size()];
445         int i = 0;
446         for (final CPFloat fl : cp_Float) {
447             cpFloat[i] = Float.floatToIntBits(fl.getFloat());
448             i++;
449         }
450         final byte[] encodedBand = encodeBandInt("cp_Float", cpFloat, Codec.UDELTA5);
451         out.write(encodedBand);
452         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Float[" + cpFloat.length + "]");
453     }
454 
455     private void writeCpInt(final OutputStream out) throws IOException, Pack200Exception {
456         PackingUtils.log("Writing " + cp_Int.size() + " Integer entries...");
457         final int[] cpInt = new int[cp_Int.size()];
458         int i = 0;
459         for (final CPInt integer : cp_Int) {
460             cpInt[i] = integer.getInt();
461             i++;
462         }
463         final byte[] encodedBand = encodeBandInt("cp_Int", cpInt, Codec.UDELTA5);
464         out.write(encodedBand);
465         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Int[" + cpInt.length + "]");
466     }
467 
468     private void writeCpLong(final OutputStream out) throws IOException, Pack200Exception {
469         PackingUtils.log("Writing " + cp_Long.size() + " Long entries...");
470         final int[] highBits = new int[cp_Long.size()];
471         final int[] loBits = new int[cp_Long.size()];
472         int i = 0;
473         for (final CPLong lng : cp_Long) {
474             final long l = lng.getLong();
475             highBits[i] = (int) (l >> 32);
476             loBits[i] = (int) l;
477             i++;
478         }
479         byte[] encodedBand = encodeBandInt("cp_Long_hi", highBits, Codec.UDELTA5);
480         out.write(encodedBand);
481         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Long_hi[" + highBits.length + "]");
482 
483         encodedBand = encodeBandInt("cp_Long_lo", loBits, Codec.DELTA5);
484         out.write(encodedBand);
485         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cp_Long_lo[" + loBits.length + "]");
486     }
487 
488     private void writeCpMethodOrField(final Set<CPMethodOrField> cp, final OutputStream out, final String name) throws IOException, Pack200Exception {
489         PackingUtils.log("Writing " + cp.size() + " Method and Field entries...");
490         final int[] cp_methodOrField_class = new int[cp.size()];
491         final int[] cp_methodOrField_desc = new int[cp.size()];
492         int i = 0;
493         for (final CPMethodOrField mOrF : cp) {
494             cp_methodOrField_class[i] = mOrF.getClassIndex();
495             cp_methodOrField_desc[i] = mOrF.getDescIndex();
496             i++;
497         }
498         byte[] encodedBand = encodeBandInt(name + "_class", cp_methodOrField_class, Codec.DELTA5);
499         out.write(encodedBand);
500         PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + name + "_class[" + cp_methodOrField_class.length + "]");
501 
502         encodedBand = encodeBandInt(name + "_desc", cp_methodOrField_desc, Codec.UDELTA5);
503         out.write(encodedBand);
504         PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + name + "_desc[" + cp_methodOrField_desc.length + "]");
505     }
506 
507     private void writeCpSignature(final OutputStream out) throws IOException, Pack200Exception {
508         PackingUtils.log("Writing " + cp_Signature.size() + " Signature entries...");
509         final int[] cpSignatureForm = new int[cp_Signature.size()];
510         final List<CPClass> classes = new ArrayList<>();
511         int i = 0;
512         for (final CPSignature cpS : cp_Signature) {
513             classes.addAll(cpS.getClasses());
514             cpSignatureForm[i] = cpS.getIndexInCpUtf8();
515             i++;
516         }
517         final int[] cpSignatureClasses = new int[classes.size()];
518         Arrays.setAll(cpSignatureClasses, j -> classes.get(j).getIndex());
519 
520         byte[] encodedBand = encodeBandInt("cpSignatureForm", cpSignatureForm, Codec.DELTA5);
521         out.write(encodedBand);
522         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpSignatureForm[" + cpSignatureForm.length + "]");
523 
524         encodedBand = encodeBandInt("cpSignatureClasses", cpSignatureClasses, Codec.UDELTA5);
525         out.write(encodedBand);
526         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpSignatureClasses[" + cpSignatureClasses.length + "]");
527     }
528 
529     private void writeCpString(final OutputStream out) throws IOException, Pack200Exception {
530         PackingUtils.log("Writing " + cp_String.size() + " String entries...");
531         final int[] cpString = new int[cp_String.size()];
532         int i = 0;
533         for (final CPString cpStr : cp_String) {
534             cpString[i] = cpStr.getIndexInCpUtf8();
535             i++;
536         }
537         final byte[] encodedBand = encodeBandInt("cpString", cpString, Codec.UDELTA5);
538         out.write(encodedBand);
539         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpString[" + cpString.length + "]");
540     }
541 
542     private void writeCpUtf8(final OutputStream out) throws IOException, Pack200Exception {
543         PackingUtils.log("Writing " + cp_Utf8.size() + " UTF8 entries...");
544         final int[] cpUtf8Prefix = new int[cp_Utf8.size() - 2];
545         final int[] cpUtf8Suffix = new int[cp_Utf8.size() - 1];
546         final List<Character> chars = new ArrayList<>();
547         final List<Integer> bigSuffix = new ArrayList<>();
548         final List<Character> bigChars = new ArrayList<>();
549         final Object[] cpUtf8Array = cp_Utf8.toArray();
550         final String first = ((CPUTF8) cpUtf8Array[1]).getUnderlyingString();
551         cpUtf8Suffix[0] = first.length();
552         addCharacters(chars, first.toCharArray());
553         for (int i = 2; i < cpUtf8Array.length; i++) {
554             final char[] previous = ((CPUTF8) cpUtf8Array[i - 1]).getUnderlyingString().toCharArray();
555             String currentStr = ((CPUTF8) cpUtf8Array[i]).getUnderlyingString();
556             final char[] current = currentStr.toCharArray();
557             int prefix = 0;
558             for (int j = 0; j < previous.length; j++) {
559                 if (previous[j] != current[j]) {
560                     break;
561                 }
562                 prefix++;
563             }
564             cpUtf8Prefix[i - 2] = prefix;
565             currentStr = currentStr.substring(prefix);
566             final char[] suffix = currentStr.toCharArray();
567             if (suffix.length > 1000) { // big suffix (1000 is arbitrary - can we
568                 // do better?)
569                 cpUtf8Suffix[i - 1] = 0;
570                 bigSuffix.add(Integer.valueOf(suffix.length));
571                 addCharacters(bigChars, suffix);
572             } else {
573                 cpUtf8Suffix[i - 1] = suffix.length;
574                 addCharacters(chars, suffix);
575             }
576         }
577         final int[] cpUtf8Chars = new int[chars.size()];
578         final int[] cpUtf8BigSuffix = new int[bigSuffix.size()];
579         final int[][] cpUtf8BigChars = new int[bigSuffix.size()][];
580         Arrays.setAll(cpUtf8Chars, i -> chars.get(i).charValue());
581         for (int i = 0; i < cpUtf8BigSuffix.length; i++) {
582             final int numBigChars = bigSuffix.get(i).intValue();
583             cpUtf8BigSuffix[i] = numBigChars;
584             cpUtf8BigChars[i] = new int[numBigChars];
585             Arrays.setAll(cpUtf8BigChars[i], j -> bigChars.remove(0).charValue());
586         }
587 
588         byte[] encodedBand = encodeBandInt("cpUtf8Prefix", cpUtf8Prefix, Codec.DELTA5);
589         out.write(encodedBand);
590         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8Prefix[" + cpUtf8Prefix.length + "]");
591 
592         encodedBand = encodeBandInt("cpUtf8Suffix", cpUtf8Suffix, Codec.UNSIGNED5);
593         out.write(encodedBand);
594         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8Suffix[" + cpUtf8Suffix.length + "]");
595 
596         encodedBand = encodeBandInt("cpUtf8Chars", cpUtf8Chars, Codec.CHAR3);
597         out.write(encodedBand);
598         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8Chars[" + cpUtf8Chars.length + "]");
599 
600         encodedBand = encodeBandInt("cpUtf8BigSuffix", cpUtf8BigSuffix, Codec.DELTA5);
601         out.write(encodedBand);
602         PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8BigSuffix[" + cpUtf8BigSuffix.length + "]");
603 
604         for (int i = 0; i < cpUtf8BigChars.length; i++) {
605             encodedBand = encodeBandInt("cpUtf8BigChars " + i, cpUtf8BigChars[i], Codec.DELTA5);
606             out.write(encodedBand);
607             PackingUtils.log("Wrote " + encodedBand.length + " bytes from cpUtf8BigChars" + i + "[" + cpUtf8BigChars[i].length + "]");
608         }
609     }
610 
611 }