| /* |
| * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package jdk.tools.jlink.internal.plugins; |
| |
| import com.sun.tools.classfile.Annotation; |
| import com.sun.tools.classfile.Attribute; |
| import com.sun.tools.classfile.Attributes; |
| import com.sun.tools.classfile.ClassFile; |
| import com.sun.tools.classfile.ConstantPool; |
| import com.sun.tools.classfile.ConstantPoolException; |
| import com.sun.tools.classfile.Field; |
| import com.sun.tools.classfile.LocalVariableTable_attribute; |
| import com.sun.tools.classfile.LocalVariableTypeTable_attribute; |
| import com.sun.tools.classfile.Method; |
| import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute; |
| import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute; |
| import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute; |
| import com.sun.tools.classfile.Signature_attribute; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import jdk.internal.jimage.decompressor.CompressIndexes; |
| import jdk.internal.jimage.decompressor.SignatureParser; |
| import jdk.internal.jimage.decompressor.StringSharingDecompressor; |
| import jdk.tools.jlink.internal.ModulePoolImpl; |
| import jdk.tools.jlink.plugin.TransformerPlugin; |
| import jdk.tools.jlink.plugin.PluginException; |
| import jdk.tools.jlink.plugin.ModuleEntry; |
| import jdk.tools.jlink.plugin.ModulePool; |
| import jdk.tools.jlink.internal.ResourcePrevisitor; |
| import jdk.tools.jlink.internal.StringTable; |
| import jdk.tools.jlink.internal.Utils; |
| |
| /** |
| * |
| * A Plugin that stores the image classes constant pool UTF_8 entries into the |
| * Image StringsTable. |
| */ |
| public class StringSharingPlugin implements TransformerPlugin, ResourcePrevisitor { |
| |
| public static final String NAME = "compact-cp"; |
| |
| private static final int[] SIZES; |
| |
| static { |
| SIZES = StringSharingDecompressor.getSizes(); |
| } |
| |
| private static final class CompactCPHelper { |
| |
| private static final class DescriptorsScanner { |
| |
| private final ClassFile cf; |
| |
| private DescriptorsScanner(ClassFile cf) { |
| this.cf = cf; |
| } |
| |
| private Set<Integer> scan() throws Exception { |
| Set<Integer> utf8Descriptors = new HashSet<>(); |
| scanConstantPool(utf8Descriptors); |
| |
| scanFields(utf8Descriptors); |
| |
| scanMethods(utf8Descriptors); |
| |
| scanAttributes(cf.attributes, utf8Descriptors); |
| |
| return utf8Descriptors; |
| } |
| |
| private void scanAttributes(Attributes attributes, |
| Set<Integer> utf8Descriptors) throws Exception { |
| for (Attribute a : attributes) { |
| if (a instanceof Signature_attribute) { |
| Signature_attribute sig = (Signature_attribute) a; |
| utf8Descriptors.add(sig.signature_index); |
| } else if (a instanceof RuntimeVisibleAnnotations_attribute) { |
| RuntimeVisibleAnnotations_attribute an |
| = (RuntimeVisibleAnnotations_attribute) a; |
| for (Annotation annotation : an.annotations) { |
| scanAnnotation(annotation, utf8Descriptors); |
| } |
| } else if (a instanceof RuntimeInvisibleAnnotations_attribute) { |
| RuntimeInvisibleAnnotations_attribute an |
| = (RuntimeInvisibleAnnotations_attribute) a; |
| for (Annotation annotation : an.annotations) { |
| scanAnnotation(annotation, utf8Descriptors); |
| } |
| } else if (a instanceof RuntimeParameterAnnotations_attribute) { |
| RuntimeParameterAnnotations_attribute rap |
| = (RuntimeParameterAnnotations_attribute) a; |
| for (Annotation[] arr : rap.parameter_annotations) { |
| for (Annotation an : arr) { |
| scanAnnotation(an, utf8Descriptors); |
| } |
| } |
| } else if (a instanceof LocalVariableTable_attribute) { |
| LocalVariableTable_attribute lvt |
| = (LocalVariableTable_attribute) a; |
| for (LocalVariableTable_attribute.Entry entry |
| : lvt.local_variable_table) { |
| utf8Descriptors.add(entry.descriptor_index); |
| } |
| } else if (a instanceof LocalVariableTypeTable_attribute) { |
| LocalVariableTypeTable_attribute lvt |
| = (LocalVariableTypeTable_attribute) a; |
| for (LocalVariableTypeTable_attribute.Entry entry |
| : lvt.local_variable_table) { |
| utf8Descriptors.add(entry.signature_index); |
| } |
| } |
| } |
| } |
| |
| private void scanAnnotation(Annotation annotation, |
| Set<Integer> utf8Descriptors) throws Exception { |
| utf8Descriptors.add(annotation.type_index); |
| for (Annotation.element_value_pair evp : annotation.element_value_pairs) { |
| utf8Descriptors.add(evp.element_name_index); |
| scanElementValue(evp.value, utf8Descriptors); |
| } |
| } |
| |
| private void scanElementValue(Annotation.element_value value, |
| Set<Integer> utf8Descriptors) throws Exception { |
| if (value instanceof Annotation.Enum_element_value) { |
| Annotation.Enum_element_value eev |
| = (Annotation.Enum_element_value) value; |
| utf8Descriptors.add(eev.type_name_index); |
| } |
| if (value instanceof Annotation.Class_element_value) { |
| Annotation.Class_element_value eev |
| = (Annotation.Class_element_value) value; |
| utf8Descriptors.add(eev.class_info_index); |
| } |
| if (value instanceof Annotation.Annotation_element_value) { |
| Annotation.Annotation_element_value aev |
| = (Annotation.Annotation_element_value) value; |
| scanAnnotation(aev.annotation_value, utf8Descriptors); |
| } |
| if (value instanceof Annotation.Array_element_value) { |
| Annotation.Array_element_value aev |
| = (Annotation.Array_element_value) value; |
| for (Annotation.element_value v : aev.values) { |
| scanElementValue(v, utf8Descriptors); |
| } |
| } |
| } |
| |
| private void scanFields(Set<Integer> utf8Descriptors) |
| throws Exception { |
| for (Field field : cf.fields) { |
| int descriptorIndex = field.descriptor.index; |
| utf8Descriptors.add(descriptorIndex); |
| scanAttributes(field.attributes, utf8Descriptors); |
| } |
| |
| } |
| |
| private void scanMethods(Set<Integer> utf8Descriptors) |
| throws Exception { |
| for (Method m : cf.methods) { |
| int descriptorIndex = m.descriptor.index; |
| utf8Descriptors.add(descriptorIndex); |
| scanAttributes(m.attributes, utf8Descriptors); |
| } |
| } |
| |
| private void scanConstantPool(Set<Integer> utf8Descriptors) |
| throws Exception { |
| for (int i = 1; i < cf.constant_pool.size(); i++) { |
| try { |
| ConstantPool.CPInfo info = cf.constant_pool.get(i); |
| if (info instanceof ConstantPool.CONSTANT_NameAndType_info) { |
| ConstantPool.CONSTANT_NameAndType_info nameAndType |
| = (ConstantPool.CONSTANT_NameAndType_info) info; |
| utf8Descriptors.add(nameAndType.type_index); |
| } |
| if (info instanceof ConstantPool.CONSTANT_MethodType_info) { |
| ConstantPool.CONSTANT_MethodType_info mt |
| = (ConstantPool.CONSTANT_MethodType_info) info; |
| utf8Descriptors.add(mt.descriptor_index); |
| } |
| |
| if (info instanceof ConstantPool.CONSTANT_Double_info |
| || info instanceof ConstantPool.CONSTANT_Long_info) { |
| i++; |
| } |
| } catch (ConstantPool.InvalidIndex ex) { |
| throw new IOException(ex); |
| } |
| } |
| } |
| } |
| |
| public byte[] transform(ModuleEntry resource, ModulePool out, |
| StringTable strings) throws IOException, Exception { |
| byte[] content = resource.getBytes(); |
| ClassFile cf; |
| try (InputStream stream = new ByteArrayInputStream(content)) { |
| cf = ClassFile.read(stream); |
| } catch (ConstantPoolException ex) { |
| throw new IOException("Compressor EX " + ex + " for " |
| + resource.getPath() + " content.length " + content.length, ex); |
| } |
| DescriptorsScanner scanner = new DescriptorsScanner(cf); |
| return optimize(resource, out, strings, scanner.scan(), content); |
| } |
| |
| @SuppressWarnings("fallthrough") |
| private byte[] optimize(ModuleEntry resource, ModulePool resources, |
| StringTable strings, |
| Set<Integer> descriptorIndexes, byte[] content) throws Exception { |
| DataInputStream stream = new DataInputStream(new ByteArrayInputStream(content)); |
| ByteArrayOutputStream outStream = new ByteArrayOutputStream(content.length); |
| DataOutputStream out = new DataOutputStream(outStream); |
| byte[] header = new byte[8]; //magic/4, minor/2, major/2 |
| stream.readFully(header); |
| out.write(header); |
| int count = stream.readUnsignedShort(); |
| out.writeShort(count); |
| for (int i = 1; i < count; i++) { |
| int tag = stream.readUnsignedByte(); |
| byte[] arr; |
| switch (tag) { |
| case ConstantPool.CONSTANT_Utf8: { |
| String original = stream.readUTF(); |
| // 2 cases, a Descriptor or a simple String |
| if (descriptorIndexes.contains(i)) { |
| SignatureParser.ParseResult parseResult |
| = SignatureParser.parseSignatureDescriptor(original); |
| List<Integer> indexes |
| = parseResult.types.stream().map((type) -> { |
| return strings.addString(type); |
| }).collect(Collectors.toList()); |
| if (!indexes.isEmpty()) { |
| out.write(StringSharingDecompressor.EXTERNALIZED_STRING_DESCRIPTOR); |
| int sigIndex = strings.addString(parseResult.formatted); |
| byte[] compressed |
| = CompressIndexes.compress(sigIndex); |
| out.write(compressed, 0, compressed.length); |
| |
| writeDescriptorReference(out, indexes); |
| continue; |
| } |
| } |
| // Put all strings in strings table. |
| writeUTF8Reference(out, strings.addString(original)); |
| |
| break; |
| } |
| |
| case ConstantPool.CONSTANT_Long: |
| case ConstantPool.CONSTANT_Double: { |
| i++; |
| } |
| default: { |
| out.write(tag); |
| int size = SIZES[tag]; |
| arr = new byte[size]; |
| stream.readFully(arr); |
| out.write(arr); |
| } |
| } |
| } |
| out.write(content, content.length - stream.available(), |
| stream.available()); |
| out.flush(); |
| |
| return outStream.toByteArray(); |
| } |
| |
| private void writeDescriptorReference(DataOutputStream out, |
| List<Integer> indexes) throws IOException { |
| List<byte[]> buffers = new ArrayList<>(); |
| int l = 0; |
| for (Integer index : indexes) { |
| byte[] buffer = CompressIndexes.compress(index); |
| l += buffer.length; |
| buffers.add(buffer); |
| } |
| ByteBuffer bb = ByteBuffer.allocate(l); |
| buffers.stream().forEach((buf) -> { |
| bb.put(buf); |
| }); |
| byte[] compressed_indices = bb.array(); |
| byte[] compressed_size = CompressIndexes. |
| compress(compressed_indices.length); |
| out.write(compressed_size, 0, compressed_size.length); |
| out.write(compressed_indices, 0, compressed_indices.length); |
| } |
| |
| private void writeUTF8Reference(DataOutputStream out, int index) |
| throws IOException { |
| out.write(StringSharingDecompressor.EXTERNALIZED_STRING); |
| byte[] compressed = CompressIndexes.compress(index); |
| out.write(compressed, 0, compressed.length); |
| } |
| } |
| |
| private Predicate<String> predicate; |
| |
| public StringSharingPlugin() throws IOException { |
| this(new String[0]); |
| } |
| |
| StringSharingPlugin(String[] patterns) throws IOException { |
| this(new ResourceFilter(patterns)); |
| } |
| |
| StringSharingPlugin(Predicate<String> predicate) { |
| this.predicate = predicate; |
| } |
| |
| @Override |
| public Set<Category> getType() { |
| Set<Category> set = new HashSet<>(); |
| set.add(Category.COMPRESSOR); |
| return Collections.unmodifiableSet(set); |
| } |
| |
| @Override |
| public void visit(ModulePool in, ModulePool result) { |
| CompactCPHelper visit = new CompactCPHelper(); |
| in.transformAndCopy((resource) -> { |
| ModuleEntry res = resource; |
| if (predicate.test(resource.getPath()) && resource.getPath().endsWith(".class")) { |
| byte[] compressed = null; |
| try { |
| compressed = visit.transform(resource, result, ((ModulePoolImpl) in).getStringTable()); |
| } catch (Exception ex) { |
| throw new PluginException(ex); |
| } |
| res = ModulePoolImpl.newCompressedResource(resource, |
| ByteBuffer.wrap(compressed), getName(), null, |
| ((ModulePoolImpl) in).getStringTable(), in.getByteOrder()); |
| } |
| return res; |
| }, result); |
| } |
| |
| @Override |
| public String getName() { |
| return NAME; |
| } |
| |
| @Override |
| public String getDescription() { |
| return PluginsResourceBundle.getDescription(NAME); |
| } |
| |
| @Override |
| public boolean hasArguments() { |
| return true; |
| } |
| |
| @Override |
| public String getArgumentsDescription() { |
| return PluginsResourceBundle.getArgument(NAME); |
| } |
| |
| @Override |
| public void configure(Map<String, String> config) { |
| try { |
| String val = config.get(NAME); |
| predicate = new ResourceFilter(Utils.listParser.apply(val)); |
| } catch (IOException ex) { |
| throw new PluginException(ex); |
| } |
| } |
| |
| @Override |
| public void previsit(ModulePool resources, StringTable strings) { |
| CompactCPHelper preVisit = new CompactCPHelper(); |
| resources.entries().forEach(resource -> { |
| if (resource.getType().equals(ModuleEntry.Type.CLASS_OR_RESOURCE) |
| && resource.getPath().endsWith(".class") && predicate.test(resource.getPath())) { |
| try { |
| preVisit.transform(resource, null, strings); |
| } catch (Exception ex) { |
| throw new PluginException(ex); |
| } |
| } |
| }); |
| } |
| } |