Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Java >= 9 #19

Merged
merged 6 commits into from
Nov 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# MatFileRW releases

### Version 3.1.0-SNAPSHOT - TBD ([javadoc](http:https://diffplug.github.io/matfilerw/javadoc/snapshot/), [snapshot](https://oss.sonatype.org/content/repositories/snapshots/com/diffplug/matsim/matfilerw/))
* Added support for Jigsaw and Java 9+ (see [#16](https://github.com/diffplug/matfilerw/issues/16)).

### Version 3.0.1 - June 28th 2017 ([javadoc](http:https://diffplug.github.io/matfilerw/javadoc/3.0.1/), [jcenter](https://bintray.com/diffplug/opensource/matfilerw/3.0.1/view))

Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jar.manifest.attributes (
'Bundle-DocURL': "https://github.com/${project.org}/${project.name}",
'Bundle-License': "https://github.com/${project.org}/${project.name}/blob/v${project.version}/LICENSE",
'-removeheaders': 'Bnd-LastModified,Bundle-Name,Created-By,Tool',
'Automatic-Module-Name': 'com.jmatio', // Jigsaw module name
)
// copy the manifest into the WC
osgiBndManifest {
Expand Down
70 changes: 7 additions & 63 deletions src/main/java/com/jmatio/io/MatFileReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand Down Expand Up @@ -323,7 +318,6 @@ public synchronized Map<String, MLArray> read(RandomAccessFile raFile, MatFileFi

FileChannel roChannel = null;
ByteBuffer buf = null;
WeakReference<MappedByteBuffer> bufferWeakRef = null;
try {
//Create a read-only memory-mapped file
roChannel = raFile.getChannel();
Expand All @@ -339,7 +333,6 @@ public synchronized Map<String, MLArray> read(RandomAccessFile raFile, MatFileFi
break;
case HEAP_BYTE_BUFFER:
int filesize = (int) roChannel.size();
System.gc();
buf = ByteBuffer.allocate(filesize);

// The following two methods couldn't be used (at least under MS Windows)
Expand All @@ -366,7 +359,6 @@ public synchronized Map<String, MLArray> read(RandomAccessFile raFile, MatFileFi
break;
case MEMORY_MAPPED_FILE:
buf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, (int) roChannel.size());
bufferWeakRef = new WeakReference<MappedByteBuffer>((MappedByteBuffer) buf);
break;
default:
throw new IllegalArgumentException("Unknown file allocation policy");
Expand All @@ -378,29 +370,19 @@ public synchronized Map<String, MLArray> read(RandomAccessFile raFile, MatFileFi
} catch (IOException e) {
throw e;
} finally {
if (buf != null && buf.isDirect()) {
// Forcefully unmap memory mapped buffer or direct buffer. This is a
// workaround for <a href="http:https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038">#4724038</a>.
// Note that subsequent accesses to the buffer will crash the runtime, so it may
// only be applied to internal buffers.
Unsafe9R.invokeCleaner(buf);
}
if (roChannel != null) {
roChannel.close();
}
if (raFile != null) {
raFile.close();
}
if (buf != null && bufferWeakRef != null && policy == MEMORY_MAPPED_FILE) {
try {
clean(buf);
} catch (Exception e) {
int GC_TIMEOUT_MS = 1000;
buf = null;
long start = System.currentTimeMillis();
while (bufferWeakRef.get() != null) {
if (System.currentTimeMillis() - start > GC_TIMEOUT_MS) {
break; //a hell cannot be unmapped - hopefully GC will
//do it's job later
}
System.gc();
Thread.yield();
}
}
}
}
}

Expand Down Expand Up @@ -660,44 +642,6 @@ private void copy(InputStream stream, ByteArrayOutputStream2 output) throws IOEx
}
}

/**
* Workaround taken from bug <a
* href="http:https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038">#4724038</a>
* to release the memory mapped byte buffer.
* <p>
* Little quote from SUN: <i>This is highly inadvisable, to put it mildly.
* It is exceedingly dangerous to forcibly unmap a mapped byte buffer that's
* visible to Java code. Doing so risks both the security and stability of
* the system</i>
* <p>
* Since the memory byte buffer used to map the file is not exposed to the
* outside world, maybe it's save to use it without being cursed by the SUN.
* Since there is no other solution this will do (don't trust voodoo GC
* invocation)
*
* @param buffer
* the buffer to be unmapped
* @throws Exception
* all kind of evil stuff
*/
private void clean(final Object buffer) throws Exception {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
Method getCleanerMethod = buffer.getClass().getMethod(
"cleaner", new Class[0]);
getCleanerMethod.setAccessible(true);
sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod
.invoke(buffer, new Object[0]);
cleaner.clean();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
}

/**
* Gets MAT-file header
*
Expand Down
129 changes: 129 additions & 0 deletions src/main/java/com/jmatio/io/Unsafe9R.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Code licensed under new-style BSD (see LICENSE).
* All code up to tags/original: Copyright (c) 2006, Wojciech Gradkowski
* All code after tags/original: Copyright (c) 2015, DiffPlug
*/
package com.jmatio.io;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;

import sun.misc.Unsafe;

/**
* Provides reflective access to new methods that were added to sun.misc.Unsafe
* in Java 9. Earlier platforms will fall back to a a behavior-equivalent
* implementation that uses available operations.
* <p>
* ========== JDK Warning: sun.misc.Unsafe ==========
* <p>
* A collection of methods for performing low-level, unsafe operations.
* Although the class and all methods are public, use of this class is
* limited because only trusted code can obtain instances of it.
*
* <em>Note:</em> It is the resposibility of the caller to make sure
* arguments are checked before methods of this class are
* called. While some rudimentary checks are performed on the input,
* the checks are best effort and when performance is an overriding
* priority, as when methods of this class are optimized by the
* runtime compiler, some or all checks (if any) may be elided. Hence,
* the caller must not rely on the checks and corresponding
* exceptions!
*
* @author Florian Enner
*/
class Unsafe9R {

/**
* Invokes the given direct byte buffer's cleaner, if any.
*
* @param directBuffer a direct byte buffer
* @throws NullPointerException if {@code directBuffer} is null
* @throws IllegalArgumentException if {@code directBuffer} is non-direct
* @throws IllegalArgumentException if {@code directBuffer} is slice or duplicate (Java 9+ only)
*/
public static void invokeCleaner(ByteBuffer directBuffer) {
if (!directBuffer.isDirect())
throw new IllegalArgumentException("buffer is non-direct");
if (useJava9)
Java9.invokeCleaner(directBuffer);
else {
Java6.invokeCleaner(directBuffer);
}
}

static {

// Get Java version
String version = System.getProperty("java.specification.version", "6");
String majorVersion = version.startsWith("1.") ? version.substring(2) : version;
useJava9 = Integer.parseInt(majorVersion) >= 9;

}

private static final boolean useJava9;

private static class Java9 {

static void invokeCleaner(ByteBuffer buffer) {
try {
INVOKE_CLEANER.invoke(UNSAFE, buffer);
} catch (Exception e) {
throw new IllegalStateException("Java 9 Cleaner failed to free DirectBuffer", e);
}
}

static final Unsafe UNSAFE;
static final Method INVOKE_CLEANER;

static {
try {
final PrivilegedExceptionAction<Unsafe> action = new PrivilegedExceptionAction<Unsafe>() {
@Override
public Unsafe run() throws Exception {
final Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
}
};
UNSAFE = AccessController.doPrivileged(action);
INVOKE_CLEANER = UNSAFE.getClass().getMethod("invokeCleaner", ByteBuffer.class);

} catch (final Exception ex) {
throw new IllegalStateException("Java 9 Cleaner not available", ex);
}

}

}

private static class Java6 {

static void invokeCleaner(ByteBuffer buffer) {
try {
Object cleaner = GET_CLEANER.invoke(buffer);
if (cleaner != null)
INVOKE_CLEANER.invoke(cleaner);
} catch (Exception e) {
throw new IllegalStateException("Java 6 Cleaner failed to free DirectBuffer", e);
}
}

static {
try {
GET_CLEANER = Class.forName("sun.nio.ch.DirectBuffer").getMethod("cleaner");
INVOKE_CLEANER = Class.forName("sun.misc.Cleaner").getMethod("clean");
} catch (Exception e) {
throw new IllegalStateException("Java 6 Cleaner not available", e);
}
}

static final Method GET_CLEANER;
static final Method INVOKE_CLEANER;

}

}