package jdk.internal.jimage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;
import java.util.stream.IntStream;
import jdk.internal.jimage.decompressor.Decompressor;
public class BasicImageReader implements AutoCloseable {
private static boolean isSystemProperty(String key, String value, String def) {
return AccessController.doPrivileged(
new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return value.equals(System.getProperty(key, def));
}
});
}
static private final boolean IS_64_BIT =
isSystemProperty("sun.arch.data.model", "64", "32");
static private final boolean USE_JVM_MAP =
isSystemProperty("jdk.image.use.jvm.map", "true", "true");
static private final boolean MAP_ALL =
isSystemProperty("jdk.image.map.all", "true", IS_64_BIT ? "true" : "false");
private final Path imagePath;
private final ByteOrder byteOrder;
private final String name;
private final ByteBuffer memoryMap;
private final FileChannel channel;
private final ImageHeader ;
private final long indexSize;
private final IntBuffer redirect;
private final IntBuffer offsets;
private final ByteBuffer locations;
private final ByteBuffer strings;
private final ImageStringsReader stringsReader;
private final Decompressor decompressor;
protected BasicImageReader(Path path, ByteOrder byteOrder)
throws IOException {
this.imagePath = Objects.requireNonNull(path);
this.byteOrder = Objects.requireNonNull(byteOrder);
this.name = this.imagePath.toString();
ByteBuffer map;
if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) {
map = NativeImageBuffer.getNativeMap(name);
} else {
map = null;
}
if (map != null && MAP_ALL) {
channel = null;
} else {
channel = FileChannel.open(imagePath, StandardOpenOption.READ);
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
if (BasicImageReader.class.getClassLoader() == null) {
try {
Class<?> fileChannelImpl =
Class.forName("sun.nio.ch.FileChannelImpl");
Method setUninterruptible =
fileChannelImpl.getMethod("setUninterruptible");
setUninterruptible.invoke(channel);
} catch (ClassNotFoundException |
NoSuchMethodException |
IllegalAccessException |
InvocationTargetException ex) {
}
}
return null;
}
});
}
if (MAP_ALL && map == null) {
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
}
ByteBuffer headerBuffer = map;
int headerSize = ImageHeader.getHeaderSize();
if (headerBuffer == null) {
headerBuffer = ByteBuffer.allocateDirect(headerSize);
if (channel.read(headerBuffer, 0L) == headerSize) {
headerBuffer.rewind();
} else {
throw new IOException("\"" + name + "\" is not an image file");
}
} else if (headerBuffer.capacity() < headerSize) {
throw new IOException("\"" + name + "\" is not an image file");
}
header = readHeader(intBuffer(headerBuffer, 0, headerSize));
indexSize = header.getIndexSize();
if (map == null) {
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize);
}
memoryMap = map.asReadOnlyBuffer();
if (memoryMap.capacity() < indexSize) {
throw new IOException("The image file \"" + name + "\" is corrupted");
}
redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize());
offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize());
locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize());
strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize());
stringsReader = new ImageStringsReader(this);
decompressor = new Decompressor();
}
protected BasicImageReader(Path imagePath) throws IOException {
this(imagePath, ByteOrder.nativeOrder());
}
public static BasicImageReader open(Path imagePath) throws IOException {
return new BasicImageReader(imagePath, ByteOrder.nativeOrder());
}
public ImageHeader () {
return header;
}
private ImageHeader (IntBuffer buffer) throws IOException {
ImageHeader result = ImageHeader.readFrom(buffer);
if (result.getMagic() != ImageHeader.MAGIC) {
throw new IOException("\"" + name + "\" is not an image file");
}
if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION ||
result.getMinorVersion() != ImageHeader.MINOR_VERSION) {
throw new IOException("The image file \"" + name + "\" is not " +
"the correct version. Major: " + result.getMajorVersion() +
". Minor: " + result.getMinorVersion());
}
return result;
}
private static ByteBuffer slice(ByteBuffer buffer, int position, int capacity) {
synchronized(buffer) {
buffer.limit(position + capacity);
buffer.position(position);
return buffer.slice();
}
}
private IntBuffer intBuffer(ByteBuffer buffer, int offset, int size) {
return slice(buffer, offset, size).order(byteOrder).asIntBuffer();
}
public static void releaseByteBuffer(ByteBuffer buffer) {
Objects.requireNonNull(buffer);
if (!MAP_ALL) {
ImageBufferCache.releaseBuffer(buffer);
}
}
public String getName() {
return name;
}
public ByteOrder getByteOrder() {
return byteOrder;
}
public Path getImagePath() {
return imagePath;
}
@Override
public void close() throws IOException {
if (channel != null) {
channel.close();
}
}
public ImageStringsReader getStrings() {
return stringsReader;
}
public synchronized ImageLocation findLocation(String module, String name) {
Objects.requireNonNull(module);
Objects.requireNonNull(name);
int count = header.getTableLength();
int index = redirect.get(ImageStringsReader.hashCode(module, name) % count);
if (index < 0) {
index = -index - 1;
} else if (index > 0) {
index = ImageStringsReader.hashCode(module, name, index) % count;
} else {
return null;
}
long[] attributes = getAttributes(offsets.get(index));
if (!ImageLocation.verify(module, name, attributes, stringsReader)) {
return null;
}
return new ImageLocation(attributes, stringsReader);
}
public synchronized ImageLocation findLocation(String name) {
Objects.requireNonNull(name);
int count = header.getTableLength();
int index = redirect.get(ImageStringsReader.hashCode(name) % count);
if (index < 0) {
index = -index - 1;
} else if (index > 0) {
index = ImageStringsReader.hashCode(name, index) % count;
} else {
return null;
}
long[] attributes = getAttributes(offsets.get(index));
if (!ImageLocation.verify(name, attributes, stringsReader)) {
return null;
}
return new ImageLocation(attributes, stringsReader);
}
public String[] getEntryNames() {
int[] attributeOffsets = new int[offsets.capacity()];
offsets.get(attributeOffsets);
return IntStream.of(attributeOffsets)
.filter(o -> o != 0)
.mapToObj(o -> ImageLocation.readFrom(this, o).getFullName())
.sorted()
.toArray(String[]::new);
}
ImageLocation getLocation(int offset) {
return ImageLocation.readFrom(this, offset);
}
public long[] getAttributes(int offset) {
if (offset < 0 || offset >= locations.limit()) {
throw new IndexOutOfBoundsException("offset");
}
ByteBuffer buffer = slice(locations, offset, locations.limit() - offset);
return ImageLocation.decompress(buffer);
}
public String getString(int offset) {
if (offset < 0 || offset >= strings.limit()) {
throw new IndexOutOfBoundsException("offset");
}
ByteBuffer buffer = slice(strings, offset, strings.limit() - offset);
return ImageStringsReader.stringFromByteBuffer(buffer);
}
private byte[] getBufferBytes(ByteBuffer buffer) {
Objects.requireNonNull(buffer);
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
return bytes;
}
private ByteBuffer readBuffer(long offset, long size) {
if (offset < 0 || Integer.MAX_VALUE <= offset) {
throw new IndexOutOfBoundsException("Bad offset: " + offset);
}
if (size < 0 || Integer.MAX_VALUE <= size) {
throw new IndexOutOfBoundsException("Bad size: " + size);
}
if (MAP_ALL) {
ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size);
buffer.order(ByteOrder.BIG_ENDIAN);
return buffer;
} else {
if (channel == null) {
throw new InternalError("Image file channel not open");
}
ByteBuffer buffer = ImageBufferCache.getBuffer(size);
int read;
try {
read = channel.read(buffer, offset);
buffer.rewind();
} catch (IOException ex) {
ImageBufferCache.releaseBuffer(buffer);
throw new RuntimeException(ex);
}
if (read != size) {
ImageBufferCache.releaseBuffer(buffer);
throw new RuntimeException("Short read: " + read +
" instead of " + size + " bytes");
}
return buffer;
}
}
public byte[] getResource(String name) {
Objects.requireNonNull(name);
ImageLocation location = findLocation(name);
return location != null ? getResource(location) : null;
}
public byte[] getResource(ImageLocation loc) {
ByteBuffer buffer = getResourceBuffer(loc);
if (buffer != null) {
byte[] bytes = getBufferBytes(buffer);
ImageBufferCache.releaseBuffer(buffer);
return bytes;
}
return null;
}
public ByteBuffer getResourceBuffer(ImageLocation loc) {
Objects.requireNonNull(loc);
long offset = loc.getContentOffset() + indexSize;
long compressedSize = loc.getCompressedSize();
long uncompressedSize = loc.getUncompressedSize();
if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) {
throw new IndexOutOfBoundsException(
"Bad compressed size: " + compressedSize);
}
if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) {
throw new IndexOutOfBoundsException(
"Bad uncompressed size: " + uncompressedSize);
}
if (compressedSize == 0) {
return readBuffer(offset, uncompressedSize);
} else {
ByteBuffer buffer = readBuffer(offset, compressedSize);
if (buffer != null) {
byte[] bytesIn = getBufferBytes(buffer);
ImageBufferCache.releaseBuffer(buffer);
byte[] bytesOut;
try {
bytesOut = decompressor.decompressResource(byteOrder,
(int strOffset) -> getString(strOffset), bytesIn);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return ByteBuffer.wrap(bytesOut);
}
}
return null;
}
public InputStream getResourceStream(ImageLocation loc) {
Objects.requireNonNull(loc);
byte[] bytes = getResource(loc);
return new ByteArrayInputStream(bytes);
}
}