1/* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package jdk.tools.jlink.internal.plugins; 26 27import com.sun.tools.classfile.Annotation; 28import com.sun.tools.classfile.Attribute; 29import com.sun.tools.classfile.Attributes; 30import com.sun.tools.classfile.ClassFile; 31import com.sun.tools.classfile.ConstantPool; 32import com.sun.tools.classfile.ConstantPoolException; 33import com.sun.tools.classfile.Field; 34import com.sun.tools.classfile.LocalVariableTable_attribute; 35import com.sun.tools.classfile.LocalVariableTypeTable_attribute; 36import com.sun.tools.classfile.Method; 37import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute; 38import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute; 39import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute; 40import com.sun.tools.classfile.Signature_attribute; 41import java.io.ByteArrayInputStream; 42import java.io.ByteArrayOutputStream; 43import java.io.DataInputStream; 44import java.io.DataOutputStream; 45import java.io.IOException; 46import java.io.InputStream; 47import java.nio.ByteBuffer; 48import java.util.ArrayList; 49import java.util.HashSet; 50import java.util.List; 51import java.util.Map; 52import java.util.Set; 53import java.util.function.Predicate; 54import java.util.stream.Collectors; 55import jdk.internal.jimage.decompressor.CompressIndexes; 56import jdk.internal.jimage.decompressor.SignatureParser; 57import jdk.internal.jimage.decompressor.StringSharingDecompressor; 58import jdk.tools.jlink.internal.ResourcePoolManager.ResourcePoolImpl; 59import jdk.tools.jlink.plugin.Plugin; 60import jdk.tools.jlink.plugin.PluginException; 61import jdk.tools.jlink.plugin.ResourcePool; 62import jdk.tools.jlink.plugin.ResourcePoolBuilder; 63import jdk.tools.jlink.plugin.ResourcePoolEntry; 64import jdk.tools.jlink.internal.ResourcePoolManager; 65import jdk.tools.jlink.internal.ResourcePrevisitor; 66import jdk.tools.jlink.internal.StringTable; 67 68/** 69 * 70 * A Plugin that stores the image classes constant pool UTF_8 entries into the 71 * Image StringsTable. 72 */ 73public class StringSharingPlugin implements Plugin, ResourcePrevisitor { 74 75 public static final String NAME = "compact-cp"; 76 77 private static final int[] SIZES; 78 79 static { 80 SIZES = StringSharingDecompressor.getSizes(); 81 } 82 83 private static final class CompactCPHelper { 84 85 private static final class DescriptorsScanner { 86 87 private final ClassFile cf; 88 89 private DescriptorsScanner(ClassFile cf) { 90 this.cf = cf; 91 } 92 93 private Set<Integer> scan() throws Exception { 94 Set<Integer> utf8Descriptors = new HashSet<>(); 95 scanConstantPool(utf8Descriptors); 96 97 scanFields(utf8Descriptors); 98 99 scanMethods(utf8Descriptors); 100 101 scanAttributes(cf.attributes, utf8Descriptors); 102 103 return utf8Descriptors; 104 } 105 106 private void scanAttributes(Attributes attributes, 107 Set<Integer> utf8Descriptors) throws Exception { 108 for (Attribute a : attributes) { 109 if (a instanceof Signature_attribute) { 110 Signature_attribute sig = (Signature_attribute) a; 111 utf8Descriptors.add(sig.signature_index); 112 } else if (a instanceof RuntimeVisibleAnnotations_attribute) { 113 RuntimeVisibleAnnotations_attribute an 114 = (RuntimeVisibleAnnotations_attribute) a; 115 for (Annotation annotation : an.annotations) { 116 scanAnnotation(annotation, utf8Descriptors); 117 } 118 } else if (a instanceof RuntimeInvisibleAnnotations_attribute) { 119 RuntimeInvisibleAnnotations_attribute an 120 = (RuntimeInvisibleAnnotations_attribute) a; 121 for (Annotation annotation : an.annotations) { 122 scanAnnotation(annotation, utf8Descriptors); 123 } 124 } else if (a instanceof RuntimeParameterAnnotations_attribute) { 125 RuntimeParameterAnnotations_attribute rap 126 = (RuntimeParameterAnnotations_attribute) a; 127 for (Annotation[] arr : rap.parameter_annotations) { 128 for (Annotation an : arr) { 129 scanAnnotation(an, utf8Descriptors); 130 } 131 } 132 } else if (a instanceof LocalVariableTable_attribute) { 133 LocalVariableTable_attribute lvt 134 = (LocalVariableTable_attribute) a; 135 for (LocalVariableTable_attribute.Entry entry 136 : lvt.local_variable_table) { 137 utf8Descriptors.add(entry.descriptor_index); 138 } 139 } else if (a instanceof LocalVariableTypeTable_attribute) { 140 LocalVariableTypeTable_attribute lvt 141 = (LocalVariableTypeTable_attribute) a; 142 for (LocalVariableTypeTable_attribute.Entry entry 143 : lvt.local_variable_table) { 144 utf8Descriptors.add(entry.signature_index); 145 } 146 } 147 } 148 } 149 150 private void scanAnnotation(Annotation annotation, 151 Set<Integer> utf8Descriptors) throws Exception { 152 utf8Descriptors.add(annotation.type_index); 153 for (Annotation.element_value_pair evp : annotation.element_value_pairs) { 154 utf8Descriptors.add(evp.element_name_index); 155 scanElementValue(evp.value, utf8Descriptors); 156 } 157 } 158 159 private void scanElementValue(Annotation.element_value value, 160 Set<Integer> utf8Descriptors) throws Exception { 161 if (value instanceof Annotation.Enum_element_value) { 162 Annotation.Enum_element_value eev 163 = (Annotation.Enum_element_value) value; 164 utf8Descriptors.add(eev.type_name_index); 165 } 166 if (value instanceof Annotation.Class_element_value) { 167 Annotation.Class_element_value eev 168 = (Annotation.Class_element_value) value; 169 utf8Descriptors.add(eev.class_info_index); 170 } 171 if (value instanceof Annotation.Annotation_element_value) { 172 Annotation.Annotation_element_value aev 173 = (Annotation.Annotation_element_value) value; 174 scanAnnotation(aev.annotation_value, utf8Descriptors); 175 } 176 if (value instanceof Annotation.Array_element_value) { 177 Annotation.Array_element_value aev 178 = (Annotation.Array_element_value) value; 179 for (Annotation.element_value v : aev.values) { 180 scanElementValue(v, utf8Descriptors); 181 } 182 } 183 } 184 185 private void scanFields(Set<Integer> utf8Descriptors) 186 throws Exception { 187 for (Field field : cf.fields) { 188 int descriptorIndex = field.descriptor.index; 189 utf8Descriptors.add(descriptorIndex); 190 scanAttributes(field.attributes, utf8Descriptors); 191 } 192 193 } 194 195 private void scanMethods(Set<Integer> utf8Descriptors) 196 throws Exception { 197 for (Method m : cf.methods) { 198 int descriptorIndex = m.descriptor.index; 199 utf8Descriptors.add(descriptorIndex); 200 scanAttributes(m.attributes, utf8Descriptors); 201 } 202 } 203 204 private void scanConstantPool(Set<Integer> utf8Descriptors) 205 throws Exception { 206 for (int i = 1; i < cf.constant_pool.size(); i++) { 207 try { 208 ConstantPool.CPInfo info = cf.constant_pool.get(i); 209 if (info instanceof ConstantPool.CONSTANT_NameAndType_info) { 210 ConstantPool.CONSTANT_NameAndType_info nameAndType 211 = (ConstantPool.CONSTANT_NameAndType_info) info; 212 utf8Descriptors.add(nameAndType.type_index); 213 } 214 if (info instanceof ConstantPool.CONSTANT_MethodType_info) { 215 ConstantPool.CONSTANT_MethodType_info mt 216 = (ConstantPool.CONSTANT_MethodType_info) info; 217 utf8Descriptors.add(mt.descriptor_index); 218 } 219 220 if (info instanceof ConstantPool.CONSTANT_Double_info 221 || info instanceof ConstantPool.CONSTANT_Long_info) { 222 i++; 223 } 224 } catch (ConstantPool.InvalidIndex ex) { 225 throw new IOException(ex); 226 } 227 } 228 } 229 } 230 231 public byte[] transform(ResourcePoolEntry resource, ResourcePoolBuilder out, 232 StringTable strings) throws IOException, Exception { 233 byte[] content = resource.contentBytes(); 234 ClassFile cf; 235 try (InputStream stream = new ByteArrayInputStream(content)) { 236 cf = ClassFile.read(stream); 237 } catch (ConstantPoolException ex) { 238 throw new IOException("Compressor EX " + ex + " for " 239 + resource.path() + " content.length " + content.length, ex); 240 } 241 DescriptorsScanner scanner = new DescriptorsScanner(cf); 242 return optimize(resource, out, strings, scanner.scan(), content); 243 } 244 245 @SuppressWarnings("fallthrough") 246 private byte[] optimize(ResourcePoolEntry resource, ResourcePoolBuilder resources, 247 StringTable strings, 248 Set<Integer> descriptorIndexes, byte[] content) throws Exception { 249 DataInputStream stream = new DataInputStream(new ByteArrayInputStream(content)); 250 ByteArrayOutputStream outStream = new ByteArrayOutputStream(content.length); 251 DataOutputStream out = new DataOutputStream(outStream); 252 byte[] header = new byte[8]; //magic/4, minor/2, major/2 253 stream.readFully(header); 254 out.write(header); 255 int count = stream.readUnsignedShort(); 256 out.writeShort(count); 257 for (int i = 1; i < count; i++) { 258 int tag = stream.readUnsignedByte(); 259 byte[] arr; 260 switch (tag) { 261 case ConstantPool.CONSTANT_Utf8: { 262 String original = stream.readUTF(); 263 // 2 cases, a Descriptor or a simple String 264 if (descriptorIndexes.contains(i)) { 265 SignatureParser.ParseResult parseResult 266 = SignatureParser.parseSignatureDescriptor(original); 267 List<Integer> indexes 268 = parseResult.types.stream().map((type) -> { 269 return strings.addString(type); 270 }).collect(Collectors.toList()); 271 if (!indexes.isEmpty()) { 272 out.write(StringSharingDecompressor.EXTERNALIZED_STRING_DESCRIPTOR); 273 int sigIndex = strings.addString(parseResult.formatted); 274 byte[] compressed 275 = CompressIndexes.compress(sigIndex); 276 out.write(compressed, 0, compressed.length); 277 278 writeDescriptorReference(out, indexes); 279 continue; 280 } 281 } 282 // Put all strings in strings table. 283 writeUTF8Reference(out, strings.addString(original)); 284 285 break; 286 } 287 288 case ConstantPool.CONSTANT_Long: 289 case ConstantPool.CONSTANT_Double: { 290 i++; 291 } 292 default: { 293 out.write(tag); 294 int size = SIZES[tag]; 295 arr = new byte[size]; 296 stream.readFully(arr); 297 out.write(arr); 298 } 299 } 300 } 301 out.write(content, content.length - stream.available(), 302 stream.available()); 303 out.flush(); 304 305 return outStream.toByteArray(); 306 } 307 308 private void writeDescriptorReference(DataOutputStream out, 309 List<Integer> indexes) throws IOException { 310 List<byte[]> buffers = new ArrayList<>(); 311 int l = 0; 312 for (Integer index : indexes) { 313 byte[] buffer = CompressIndexes.compress(index); 314 l += buffer.length; 315 buffers.add(buffer); 316 } 317 ByteBuffer bb = ByteBuffer.allocate(l); 318 buffers.stream().forEach((buf) -> { 319 bb.put(buf); 320 }); 321 byte[] compressed_indices = bb.array(); 322 byte[] compressed_size = CompressIndexes. 323 compress(compressed_indices.length); 324 out.write(compressed_size, 0, compressed_size.length); 325 out.write(compressed_indices, 0, compressed_indices.length); 326 } 327 328 private void writeUTF8Reference(DataOutputStream out, int index) 329 throws IOException { 330 out.write(StringSharingDecompressor.EXTERNALIZED_STRING); 331 byte[] compressed = CompressIndexes.compress(index); 332 out.write(compressed, 0, compressed.length); 333 } 334 } 335 336 private Predicate<String> predicate; 337 338 public StringSharingPlugin() { 339 this((path) -> true); 340 } 341 342 StringSharingPlugin(Predicate<String> predicate) { 343 this.predicate = predicate; 344 } 345 346 @Override 347 public Category getType() { 348 return Category.COMPRESSOR; 349 } 350 351 @Override 352 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder result) { 353 CompactCPHelper visit = new CompactCPHelper(); 354 in.transformAndCopy((resource) -> { 355 ResourcePoolEntry res = resource; 356 if (predicate.test(resource.path()) && resource.path().endsWith(".class")) { 357 byte[] compressed = null; 358 try { 359 compressed = visit.transform(resource, result, ((ResourcePoolImpl)in).getStringTable()); 360 } catch (Exception ex) { 361 throw new PluginException(ex); 362 } 363 res = ResourcePoolManager.newCompressedResource(resource, 364 ByteBuffer.wrap(compressed), getName(), null, 365 ((ResourcePoolImpl)in).getStringTable(), in.byteOrder()); 366 } 367 return res; 368 }, result); 369 370 return result.build(); 371 } 372 373 @Override 374 public String getName() { 375 return NAME; 376 } 377 378 @Override 379 public String getDescription() { 380 return PluginsResourceBundle.getDescription(NAME); 381 } 382 383 @Override 384 public boolean hasArguments() { 385 return true; 386 } 387 388 @Override 389 public String getArgumentsDescription() { 390 return PluginsResourceBundle.getArgument(NAME); 391 } 392 393 @Override 394 public void configure(Map<String, String> config) { 395 predicate = ResourceFilter.includeFilter(config.get(NAME)); 396 } 397 398 @Override 399 public void previsit(ResourcePool resources, StringTable strings) { 400 CompactCPHelper preVisit = new CompactCPHelper(); 401 resources.entries().forEach(resource -> { 402 if (resource.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE) 403 && resource.path().endsWith(".class") && predicate.test(resource.path())) { 404 try { 405 preVisit.transform(resource, null, strings); 406 } catch (Exception ex) { 407 throw new PluginException(ex); 408 } 409 } 410 }); 411 } 412} 413