1/*
2 * Copyright (c) 2014, 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.DataInputStream;
28import java.io.IOException;
29import java.nio.ByteBuffer;
30import java.util.ArrayList;
31import java.util.List;
32
33/**
34 *
35 * Index compressor. Use the minimal amount of bytes required to store
36 * an integer.
37 *
38 * @implNote This class needs to maintain JDK 8 source compatibility.
39 *
40 * It is used internally in the JDK to implement jimage/jrtfs access,
41 * but also compiled and delivered as part of the jrtfs.jar to support access
42 * to the jimage file provided by the shipped JDK by tools running on JDK 8.
43 */
44public class CompressIndexes {
45    private static final int COMPRESSED_FLAG = 1 << (Byte.SIZE - 1);
46    private static final int HEADER_WIDTH = 3;
47    private static final int HEADER_SHIFT = Byte.SIZE - HEADER_WIDTH;
48
49    public static List<Integer> decompressFlow(byte[] values) {
50        List<Integer> lst = new ArrayList<>();
51
52        for (int i = 0; i < values.length; i += getHeaderLength(values[i])) {
53            int decompressed = decompress(values, i);
54            lst.add(decompressed);
55        }
56
57        return lst;
58    }
59
60    public static int readInt(DataInputStream cr) throws IOException {
61        // Get header byte.
62        byte header = cr.readByte();
63        // Determine size.
64        int size = getHeaderLength(header);
65        // Prepare result.
66        int result = getHeaderValue(header);
67
68        // For each value byte
69        for (int i = 1; i < size; i++) {
70            // Merge byte value.
71            result <<= Byte.SIZE;
72            result |= cr.readByte() & 0xFF;
73        }
74
75        return result;
76    }
77
78    private static boolean isCompressed(byte b) {
79        return (b & COMPRESSED_FLAG) != 0;
80    }
81
82    private static int getHeaderLength(byte b) {
83        return isCompressed(b) ? (b >> HEADER_SHIFT) & 3 : Integer.BYTES;
84    }
85
86    private static int getHeaderValue(byte b) {
87        return isCompressed(b) ? b & (1 << HEADER_SHIFT) - 1 : b;
88    }
89
90    public static int decompress(byte[] value, int offset) {
91        // Get header byte.
92        byte header = value[offset];
93        // Determine size.
94        int size = getHeaderLength(header);
95        // Prepare result.
96        int result = getHeaderValue(header);
97
98        // For each value byte
99        for (int i = 1; i < size; i++) {
100            // Merge byte value.
101            result <<= Byte.SIZE;
102            result |= value[offset + i] & 0xFF;
103        }
104
105        return result;
106    }
107
108    public static byte[] compress(int value) {
109        // Only positive values are supported.
110        if (value < 0) {
111            throw new  IllegalArgumentException("value < 0");
112        }
113
114        // Determine number of significant digits.
115        int width = 32 - Integer.numberOfLeadingZeros(value);
116        // Determine number of byte to represent.  Allow for header if
117        // compressed.
118        int size = Math.min(((width + HEADER_WIDTH - 1) >> 3) + 1, Integer.BYTES);
119
120        // Allocate result buffer.
121        byte[] result = new byte[size];
122
123        // Insert significant bytes in result.
124        for (int i = 0; i < size; i++) {
125            result[i] = (byte)(value >> ((size - i - 1) * Byte.SIZE));
126        }
127
128        // If compressed, mark and insert size.
129        if (size < Integer.BYTES) {
130            result[0] |= (byte)(COMPRESSED_FLAG | (size << HEADER_SHIFT));
131        }
132
133        return result;
134    }
135
136}
137