1/*
2 * Copyright (c) 2015, 2016, 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.internal.jimage.decompressor;
26
27import java.io.ByteArrayInputStream;
28import java.io.ByteArrayOutputStream;
29import java.io.DataInputStream;
30import java.io.DataOutputStream;
31import java.io.IOException;
32import java.nio.ByteBuffer;
33import java.nio.ByteOrder;
34import java.util.Arrays;
35import java.util.List;
36import java.util.Properties;
37
38/**
39 *
40 * A Decompressor that reconstructs the constant pool of classes.
41 *
42 * @implNote This class needs to maintain JDK 8 source compatibility.
43 *
44 * It is used internally in the JDK to implement jimage/jrtfs access,
45 * but also compiled and delivered as part of the jrtfs.jar to support access
46 * to the jimage file provided by the shipped JDK by tools running on JDK 8.
47 */
48public class StringSharingDecompressor implements ResourceDecompressor {
49
50    public static final int EXTERNALIZED_STRING = 23;
51    public static final int EXTERNALIZED_STRING_DESCRIPTOR = 25;
52
53    private static final int CONSTANT_Utf8 = 1;
54    private static final int CONSTANT_Integer = 3;
55    private static final int CONSTANT_Float = 4;
56    private static final int CONSTANT_Long = 5;
57    private static final int CONSTANT_Double = 6;
58    private static final int CONSTANT_Class = 7;
59    private static final int CONSTANT_String = 8;
60    private static final int CONSTANT_Fieldref = 9;
61    private static final int CONSTANT_Methodref = 10;
62    private static final int CONSTANT_InterfaceMethodref = 11;
63    private static final int CONSTANT_NameAndType = 12;
64    private static final int CONSTANT_MethodHandle = 15;
65    private static final int CONSTANT_MethodType = 16;
66    private static final int CONSTANT_InvokeDynamic = 18;
67    private static final int CONSTANT_Module = 19;
68    private static final int CONSTANT_Package = 20;
69
70    private static final int[] SIZES = new int[21];
71
72    static {
73
74        //SIZES[CONSTANT_Utf8] = XXX;
75        SIZES[CONSTANT_Integer] = 4;
76        SIZES[CONSTANT_Float] = 4;
77        SIZES[CONSTANT_Long] = 8;
78        SIZES[CONSTANT_Double] = 8;
79        SIZES[CONSTANT_Class] = 2;
80        SIZES[CONSTANT_String] = 2;
81        SIZES[CONSTANT_Fieldref] = 4;
82        SIZES[CONSTANT_Methodref] = 4;
83        SIZES[CONSTANT_InterfaceMethodref] = 4;
84        SIZES[CONSTANT_NameAndType] = 4;
85        SIZES[CONSTANT_MethodHandle] = 3;
86        SIZES[CONSTANT_MethodType] = 2;
87        SIZES[CONSTANT_InvokeDynamic] = 4;
88        SIZES[CONSTANT_Module] = 2;
89        SIZES[CONSTANT_Package] = 2;
90    }
91
92    public static int[] getSizes() {
93        return SIZES.clone();
94    }
95
96    @SuppressWarnings("fallthrough")
97    public static byte[] normalize(StringsProvider provider, byte[] transformed,
98            int offset) throws IOException {
99        DataInputStream stream = new DataInputStream(new ByteArrayInputStream(transformed,
100                offset, transformed.length - offset));
101        ByteArrayOutputStream outStream = new ByteArrayOutputStream(transformed.length);
102        DataOutputStream out = new DataOutputStream(outStream);
103        byte[] header = new byte[8]; //maginc/4, minor/2, major/2
104        stream.readFully(header);
105        out.write(header);
106        int count = stream.readUnsignedShort();
107        out.writeShort(count);
108        for (int i = 1; i < count; i++) {
109            int tag = stream.readUnsignedByte();
110            byte[] arr;
111            switch (tag) {
112                case CONSTANT_Utf8: {
113                    out.write(tag);
114                    String utf = stream.readUTF();
115                    out.writeUTF(utf);
116                    break;
117                }
118
119                case EXTERNALIZED_STRING: {
120                    int index = CompressIndexes.readInt(stream);
121                    String orig = provider.getString(index);
122                    out.write(CONSTANT_Utf8);
123                    out.writeUTF(orig);
124                    break;
125                }
126
127                case EXTERNALIZED_STRING_DESCRIPTOR: {
128                    String orig = reconstruct(provider, stream);
129                    out.write(CONSTANT_Utf8);
130                    out.writeUTF(orig);
131                    break;
132                }
133                case CONSTANT_Long:
134                case CONSTANT_Double: {
135                    i++;
136                }
137                default: {
138                    out.write(tag);
139                    int size = SIZES[tag];
140                    arr = new byte[size];
141                    stream.readFully(arr);
142                    out.write(arr);
143                }
144            }
145        }
146        out.write(transformed, transformed.length - stream.available(),
147                stream.available());
148        out.flush();
149
150        return outStream.toByteArray();
151    }
152
153    private static String reconstruct(StringsProvider reader, DataInputStream cr)
154            throws IOException {
155        int descIndex = CompressIndexes.readInt(cr);
156        String desc = reader.getString(descIndex);
157        byte[] encodedDesc = getEncoded(desc);
158        int indexes_length = CompressIndexes.readInt(cr);
159        byte[] bytes = new byte[indexes_length];
160        cr.readFully(bytes);
161        List<Integer> indices = CompressIndexes.decompressFlow(bytes);
162        ByteBuffer buffer = ByteBuffer.allocate(encodedDesc.length * 2);
163        buffer.order(ByteOrder.BIG_ENDIAN);
164        int argIndex = 0;
165        for (byte c : encodedDesc) {
166            if (c == 'L') {
167                buffer = safeAdd(buffer, c);
168                int index = indices.get(argIndex);
169                argIndex += 1;
170                String pkg = reader.getString(index);
171                if (pkg.length() > 0) {
172                    pkg = pkg + "/";
173                    byte[] encoded = getEncoded(pkg);
174                    buffer = safeAdd(buffer, encoded);
175                }
176                int classIndex = indices.get(argIndex);
177                argIndex += 1;
178                String clazz = reader.getString(classIndex);
179                byte[] encoded = getEncoded(clazz);
180                buffer = safeAdd(buffer, encoded);
181            } else {
182                buffer = safeAdd(buffer, c);
183            }
184        }
185
186        byte[] encoded = buffer.array();
187        ByteBuffer result = ByteBuffer.allocate(encoded.length + 2);
188        result.order(ByteOrder.BIG_ENDIAN);
189        result.putShort((short) buffer.position());
190        result.put(encoded, 0, buffer.position());
191        ByteArrayInputStream stream = new ByteArrayInputStream(result.array());
192        DataInputStream inStream = new DataInputStream(stream);
193        String str = inStream.readUTF();
194        return str;
195    }
196
197    public static byte[] getEncoded(String pre) throws IOException {
198        ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
199        DataOutputStream resultOut = new DataOutputStream(resultStream);
200        resultOut.writeUTF(pre);
201        byte[] content = resultStream.toByteArray();
202        // first 2 items are length;
203        if (content.length <= 2) {
204            return new byte[0];
205        }
206        return Arrays.copyOfRange(content, 2, content.length);
207    }
208
209    private static ByteBuffer safeAdd(ByteBuffer current, byte b) {
210        byte[] bytes = {b};
211        return safeAdd(current, bytes);
212    }
213
214    private static ByteBuffer safeAdd(ByteBuffer current, byte[] bytes) {
215        if (current.remaining() < bytes.length) {
216            ByteBuffer newBuffer = ByteBuffer.allocate((current.capacity()
217                    + bytes.length) * 2);
218            newBuffer.order(ByteOrder.BIG_ENDIAN);
219            newBuffer.put(current.array(), 0, current.position());
220            current = newBuffer;
221        }
222        current.put(bytes);
223        return current;
224    }
225
226    @Override
227    public String getName() {
228        return StringSharingDecompressorFactory.NAME;
229    }
230
231    public StringSharingDecompressor(Properties properties) {
232
233    }
234
235    @Override
236    public byte[] decompress(StringsProvider reader, byte[] content,
237            int offset, long originalSize) throws Exception {
238        return normalize(reader, content, offset);
239    }
240}
241