blob: b3a1e7cf3cf5040dc29695ca1fa01401312182d2 [file] [log] [blame]
/*
* 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);
}
}
});
}
}