1/*
2 * Copyright (c) 2014, 2017, 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 */
25
26package jdk.internal.jimage;
27
28import java.nio.ByteBuffer;
29import java.util.Objects;
30
31/**
32 * @implNote This class needs to maintain JDK 8 source compatibility.
33 *
34 * It is used internally in the JDK to implement jimage/jrtfs access,
35 * but also compiled and delivered as part of the jrtfs.jar to support access
36 * to the jimage file provided by the shipped JDK by tools running on JDK 8.
37 */
38public class ImageLocation {
39    public static final int ATTRIBUTE_END = 0;
40    public static final int ATTRIBUTE_MODULE = 1;
41    public static final int ATTRIBUTE_PARENT = 2;
42    public static final int ATTRIBUTE_BASE = 3;
43    public static final int ATTRIBUTE_EXTENSION = 4;
44    public static final int ATTRIBUTE_OFFSET = 5;
45    public static final int ATTRIBUTE_COMPRESSED = 6;
46    public static final int ATTRIBUTE_UNCOMPRESSED = 7;
47    public static final int ATTRIBUTE_COUNT = 8;
48
49    protected final long[] attributes;
50
51    protected final ImageStrings strings;
52
53    public ImageLocation(long[] attributes, ImageStrings strings) {
54        this.attributes = Objects.requireNonNull(attributes);
55        this.strings = Objects.requireNonNull(strings);
56    }
57
58    ImageStrings getStrings() {
59        return strings;
60    }
61
62    static long[] decompress(ByteBuffer bytes) {
63        Objects.requireNonNull(bytes);
64        long[] attributes = new long[ATTRIBUTE_COUNT];
65
66        if (bytes != null) {
67            while (bytes.hasRemaining()) {
68                int data = bytes.get() & 0xFF;
69                int kind = data >>> 3;
70
71                if (kind == ATTRIBUTE_END) {
72                    break;
73                }
74
75                if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) {
76                    throw new InternalError(
77                        "Invalid jimage attribute kind: " + kind);
78                }
79
80                int length = (data & 0x7) + 1;
81                long value = 0;
82
83                for (int j = 0; j < length; j++) {
84                    value <<= 8;
85
86                    if (!bytes.hasRemaining()) {
87                        throw new InternalError("Missing jimage attribute data");
88                    }
89
90                    value |= bytes.get() & 0xFF;
91                }
92
93                 attributes[kind] = value;
94            }
95        }
96
97        return attributes;
98    }
99
100    public static byte[] compress(long[] attributes) {
101        Objects.requireNonNull(attributes);
102        ImageStream stream = new ImageStream(16);
103
104        for (int kind = ATTRIBUTE_END + 1; kind < ATTRIBUTE_COUNT; kind++) {
105            long value = attributes[kind];
106
107            if (value != 0) {
108                int n = (63 - Long.numberOfLeadingZeros(value)) >> 3;
109                stream.put((kind << 3) | n);
110
111                for (int i = n; i >= 0; i--) {
112                    stream.put((int)(value >> (i << 3)));
113                }
114            }
115        }
116
117        stream.put(ATTRIBUTE_END << 3);
118
119        return stream.toArray();
120     }
121
122    public boolean verify(String name) {
123        return verify(name, attributes, strings);
124    }
125
126    /**
127     * A simpler verification would be {@code name.equals(getFullName())}, but
128     * by not creating the full name and enabling early returns we allocate
129     * fewer objects. Could possibly be made allocation free by extending
130     * ImageStrings to test if strings at an offset match the name region.
131     */
132    static boolean verify(String name, long[] attributes, ImageStrings strings) {
133        Objects.requireNonNull(name);
134        final int length = name.length();
135        int index = 0;
136        int moduleOffset = (int)attributes[ATTRIBUTE_MODULE];
137        if (moduleOffset != 0) {
138            String module = strings.get(moduleOffset);
139            final int moduleLen = module.length();
140            index = moduleLen + 1;
141            if (length <= index
142                    || name.charAt(0) != '/'
143                    || !name.regionMatches(1, module, 0, moduleLen)
144                    || name.charAt(index++) != '/') {
145                return false;
146            }
147        }
148
149        return verifyName(name, index, length, attributes, strings);
150    }
151
152    static boolean verify(String module, String name, long[] attributes,
153            ImageStrings strings) {
154        Objects.requireNonNull(module);
155        Objects.requireNonNull(name);
156        int moduleOffset = (int)attributes[ATTRIBUTE_MODULE];
157        if (moduleOffset != 0) {
158            if (!module.equals(strings.get(moduleOffset))) {
159                return false;
160            }
161        }
162
163        return verifyName(name, 0, name.length(), attributes, strings);
164    }
165
166    private static boolean verifyName(String name, int index, int length,
167            long[] attributes, ImageStrings strings) {
168
169        int parentOffset = (int) attributes[ATTRIBUTE_PARENT];
170        if (parentOffset != 0) {
171            String parent = strings.get(parentOffset);
172            final int parentLen = parent.length();
173            if (!name.regionMatches(index, parent, 0, parentLen)) {
174                return false;
175            }
176            index += parentLen;
177            if (length <= index || name.charAt(index++) != '/') {
178                return false;
179            }
180        }
181        String base = strings.get((int) attributes[ATTRIBUTE_BASE]);
182        final int baseLen = base.length();
183        if (!name.regionMatches(index, base, 0, baseLen)) {
184            return false;
185        }
186        index += baseLen;
187        int extOffset = (int) attributes[ATTRIBUTE_EXTENSION];
188        if (extOffset != 0) {
189            String extension = strings.get(extOffset);
190            int extLen = extension.length();
191            if (length <= index
192                    || name.charAt(index++) != '.'
193                    || !name.regionMatches(index, extension, 0, extLen)) {
194                return false;
195            }
196            index += extLen;
197        }
198        return length == index;
199    }
200
201    long getAttribute(int kind) {
202        if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) {
203            throw new InternalError(
204                "Invalid jimage attribute kind: " + kind);
205        }
206
207        return attributes[kind];
208    }
209
210    String getAttributeString(int kind) {
211        if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) {
212            throw new InternalError(
213                "Invalid jimage attribute kind: " + kind);
214        }
215
216        return getStrings().get((int)attributes[kind]);
217    }
218
219    public String getModule() {
220        return getAttributeString(ATTRIBUTE_MODULE);
221    }
222
223    public int getModuleOffset() {
224        return (int)getAttribute(ATTRIBUTE_MODULE);
225    }
226
227    public String getBase() {
228        return getAttributeString(ATTRIBUTE_BASE);
229    }
230
231    public int getBaseOffset() {
232        return (int)getAttribute(ATTRIBUTE_BASE);
233    }
234
235    public String getParent() {
236        return getAttributeString(ATTRIBUTE_PARENT);
237    }
238
239    public int getParentOffset() {
240        return (int)getAttribute(ATTRIBUTE_PARENT);
241    }
242
243    public String getExtension() {
244        return getAttributeString(ATTRIBUTE_EXTENSION);
245    }
246
247    public int getExtensionOffset() {
248        return (int)getAttribute(ATTRIBUTE_EXTENSION);
249    }
250
251    public String getFullName() {
252        return getFullName(false);
253    }
254
255    public String getFullName(boolean modulesPrefix) {
256        StringBuilder builder = new StringBuilder();
257
258        if (getModuleOffset() != 0) {
259            if (modulesPrefix) {
260                builder.append("/modules");
261            }
262
263            builder.append('/');
264            builder.append(getModule());
265            builder.append('/');
266        }
267
268        if (getParentOffset() != 0) {
269            builder.append(getParent());
270            builder.append('/');
271        }
272
273        builder.append(getBase());
274
275        if (getExtensionOffset() != 0) {
276            builder.append('.');
277            builder.append(getExtension());
278        }
279
280        return builder.toString();
281    }
282
283    String buildName(boolean includeModule, boolean includeParent,
284            boolean includeName) {
285        StringBuilder builder = new StringBuilder();
286
287        if (includeModule && getModuleOffset() != 0) {
288            builder.append("/modules/");
289            builder.append(getModule());
290         }
291
292        if (includeParent && getParentOffset() != 0) {
293            builder.append('/');
294            builder.append(getParent());
295        }
296
297        if (includeName) {
298            if (includeModule || includeParent) {
299                builder.append('/');
300            }
301
302            builder.append(getBase());
303
304            if (getExtensionOffset() != 0) {
305                builder.append('.');
306                builder.append(getExtension());
307            }
308        }
309
310        return builder.toString();
311   }
312
313    public long getContentOffset() {
314        return getAttribute(ATTRIBUTE_OFFSET);
315    }
316
317    public long getCompressedSize() {
318        return getAttribute(ATTRIBUTE_COMPRESSED);
319    }
320
321    public long getUncompressedSize() {
322        return getAttribute(ATTRIBUTE_UNCOMPRESSED);
323    }
324
325    static ImageLocation readFrom(BasicImageReader reader, int offset) {
326        Objects.requireNonNull(reader);
327        long[] attributes = reader.getAttributes(offset);
328        ImageStringsReader strings = reader.getStrings();
329
330        return new ImageLocation(attributes, strings);
331    }
332}
333