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