[FEATURE] Use Java 17

This commit is contained in:
Gravita 2022-12-22 19:40:48 +07:00
parent a0ac58f0b5
commit 1cbf95f815
15 changed files with 371 additions and 766 deletions

View file

@ -1,4 +1,4 @@
{
"features": [],
"features": ["java17"],
"info": []
}

View file

@ -10,11 +10,11 @@
}
}
javafx {
version = "12"
version = "17"
modules = ['javafx.controls', 'javafx.fxml']
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
sourceCompatibility = '17'
targetCompatibility = '17'
configurations {
bundle

View file

@ -1,5 +1,5 @@
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
sourceCompatibility = '17'
targetCompatibility = '17'
dependencies {
api project(':LauncherCore')
@ -15,28 +15,12 @@ api project(':LauncherCore')
}
sourceSets {
java11 {
java {
srcDirs = ['src/main/java11']
}
dependencies {
java11Implementation files(sourceSets.main.output.classesDirs) { builtBy compileJava }
}
}
}
jar {
into('META-INF/versions/11') {
from sourceSets.java11.output
}
archiveClassifier.set('clean')
}
compileJava11Java {
sourceCompatibility = 11
targetCompatibility = 11
}
task sourcesJar(type: Jar) {
from sourceSets.main.allJava
archiveClassifier.set('sources')

View file

@ -1,5 +1,5 @@
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
sourceCompatibility = '17'
targetCompatibility = '17'
dependencies {
compileOnly group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
@ -22,26 +22,10 @@
}
}
sourceSets {
java11 {
java {
srcDirs = ['src/main/java11']
}
dependencies {
java11Implementation group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
java11Implementation files(sourceSets.main.output.classesDirs) { builtBy compileJava }
}
}
}
jar {
into('META-INF/versions/11') {
from sourceSets.java11.output
}
archiveClassifier.set('clean')
}
compileJava11Java {
sourceCompatibility = 11
targetCompatibility = 11
}
task sourcesJar(type: Jar) {
from sourceSets.main.allJava

View file

@ -2,14 +2,17 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public final class HTTPRequest {
private static final int TIMEOUT = 10000;
@ -22,36 +25,36 @@ public static JsonElement jsonRequest(JsonElement request, URL url) throws IOExc
}
public static JsonElement jsonRequest(JsonElement request, String method, URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
if (request != null) connection.setDoOutput(true);
connection.setRequestMethod(method);
if (request != null) connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
if (request != null) connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36");
connection.setRequestProperty("Accept", "application/json");
if (TIMEOUT > 0)
connection.setConnectTimeout(TIMEOUT);
if (request != null)
try (OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8)) {
writer.write(request.toString());
writer.flush();
}
InputStreamReader reader;
int statusCode = connection.getResponseCode();
if (200 <= statusCode && statusCode < 300)
reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8);
else
reader = new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8);
HttpClient client = HttpClient.newBuilder()
.build();
HttpRequest.BodyPublisher publisher;
if (request != null) {
publisher = HttpRequest.BodyPublishers.ofString(request.toString());
} else {
publisher = HttpRequest.BodyPublishers.noBody();
}
try {
return JsonParser.parseReader(reader);
} catch (Exception e) {
if (200 > statusCode || statusCode > 300) {
LogHelper.error("JsonRequest failed. Server response code %d", statusCode);
throw new IOException(e);
HttpRequest request1 = HttpRequest.newBuilder()
.method(method, publisher)
.uri(url.toURI())
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
.timeout(Duration.ofMillis(TIMEOUT))
.build();
HttpResponse<InputStream> response = client.send(request1, HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode();
try {
return JsonParser.parseReader(IOHelper.newReader(response.body()));
} catch (Exception e) {
if (200 > statusCode || statusCode > 300) {
LogHelper.error("JsonRequest failed. Server response code %d", statusCode);
throw new IOException(e);
}
return null;
}
return null;
} catch (URISyntaxException | InterruptedException e) {
throw new IOException(e);
}
}
}

View file

@ -1,57 +1,160 @@
package pro.gravit.utils;
import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.LauncherInject;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class Downloader {
private final CompletableFuture<Void> future;
private final AsyncDownloader asyncDownloader;
private Downloader(CompletableFuture<Void> future, AsyncDownloader downloader) {
this.future = future;
this.asyncDownloader = downloader;
@LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning;
@LauncherInject("launcher.noHttp2")
private static boolean isNoHttp2;
protected final HttpClient client;
protected final ExecutorService executor;
protected final LinkedList<DownloadTask> tasks = new LinkedList<>();
protected CompletableFuture<Void> future;
protected Downloader(HttpClient client, ExecutorService executor) {
this.client = client;
this.executor = executor;
}
public static Downloader downloadList(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
final boolean closeExecutor;
LogHelper.info("Download with legacy mode");
boolean closeExecutor = false;
LogHelper.info("Download with Java 11+ HttpClient");
if (executor == null) {
executor = Executors.newWorkStealingPool(4);
executor = Executors.newWorkStealingPool(Math.min(3, threads));
closeExecutor = true;
} else {
closeExecutor = false;
}
AsyncDownloader asyncDownloader = new AsyncDownloader((diff) -> {
if (callback != null) {
callback.apply(diff);
}
});
List<List<AsyncDownloader.SizedFile>> list = asyncDownloader.sortFiles(files, threads);
CompletableFuture<Void> future = CompletableFuture.allOf(asyncDownloader.runDownloadList(list, baseURL, targetDir, executor));
Downloader downloader = newDownloader(executor);
downloader.future = downloader.downloadFiles(files, baseURL, targetDir, callback, executor, threads);
if (closeExecutor) {
ExecutorService finalExecutor = executor;
downloader.future = downloader.future.thenAccept(e -> finalExecutor.shutdownNow());
}
return downloader;
}
ExecutorService finalExecutor = executor;
return new Downloader(future.thenAccept(e -> {
if (closeExecutor) {
finalExecutor.shutdownNow();
public static Downloader newDownloader(ExecutorService executor) {
if (executor == null) {
throw new NullPointerException();
}
HttpClient.Builder builder = HttpClient.newBuilder()
.version(isNoHttp2 ? HttpClient.Version.HTTP_1_1 : HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.executor(executor);
if (isCertificatePinning) {
try {
builder.sslContext(AsyncDownloader.makeSSLContext());
} catch (Exception e) {
throw new SecurityException(e);
}
}), asyncDownloader);
}
HttpClient client = builder.build();
return new Downloader(client, executor);
}
public void cancel() {
for (DownloadTask task : tasks) {
if (!task.isCompleted()) {
task.cancel();
}
}
tasks.clear();
executor.shutdownNow();
}
public boolean isCanceled() {
return executor.isTerminated();
}
public CompletableFuture<Void> getFuture() {
return future;
}
public void cancel() {
this.asyncDownloader.isClosed = true;
public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
// URI scheme
URI baseUri = new URI(baseURL);
Collections.shuffle(files);
Queue<AsyncDownloader.SizedFile> queue = new ConcurrentLinkedDeque<>(files);
CompletableFuture<Void> future = new CompletableFuture<>();
AtomicInteger currentThreads = new AtomicInteger(threads);
ConsumerObject consumerObject = new ConsumerObject();
Consumer<HttpResponse<Path>> next = e -> {
if (callback != null && e != null) {
callback.onComplete(e.body());
}
AsyncDownloader.SizedFile file = queue.poll();
if (file == null) {
if (currentThreads.decrementAndGet() == 0)
future.complete(null);
return;
}
try {
DownloadTask task = sendAsync(file, baseUri, targetDir, callback);
task.completableFuture.thenAccept(consumerObject.next).exceptionally(ec -> {
future.completeExceptionally(ec);
return null;
});
} catch (Exception exception) {
LogHelper.error(exception);
future.completeExceptionally(exception);
}
};
consumerObject.next = next;
for (int i = 0; i < threads; ++i) {
next.accept(null);
}
return future;
}
public boolean isCanceled() {
return this.asyncDownloader.isClosed;
protected DownloadTask sendAsync(AsyncDownloader.SizedFile file, URI baseUri, Path targetDir, DownloadCallback callback) throws Exception {
IOHelper.createParentDirs(targetDir.resolve(file.filePath));
ProgressTrackingBodyHandler<Path> bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback);
CompletableFuture<HttpResponse<Path>> future = client.sendAsync(makeHttpRequest(baseUri, file.urlPath), bodyHandler);
var ref = new Object() {
DownloadTask task = null;
};
ref.task = new DownloadTask(bodyHandler, future.thenApply((e) -> {
tasks.remove(ref.task);
return e;
}));
tasks.add(ref.task);
return ref.task;
}
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
String scheme = baseUri.getScheme();
String host = baseUri.getHost();
int port = baseUri.getPort();
if (port != -1)
host = host + ":" + port;
String path = baseUri.getPath();
return HttpRequest.newBuilder()
.GET()
.uri(new URI(scheme, host, path + filePath, "", ""))
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
.build();
}
protected ProgressTrackingBodyHandler<Path> makeBodyHandler(Path file, DownloadCallback callback) {
return new ProgressTrackingBodyHandler<>(HttpResponse.BodyHandlers.ofFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE), callback);
}
public interface DownloadCallback {
@ -59,4 +162,105 @@ public interface DownloadCallback {
void onComplete(Path path);
}
private static class ConsumerObject {
Consumer<HttpResponse<Path>> next = null;
}
public static class DownloadTask {
public final ProgressTrackingBodyHandler<Path> bodyHandler;
public final CompletableFuture<HttpResponse<Path>> completableFuture;
public DownloadTask(ProgressTrackingBodyHandler<Path> bodyHandler, CompletableFuture<HttpResponse<Path>> completableFuture) {
this.bodyHandler = bodyHandler;
this.completableFuture = completableFuture;
}
public boolean isCompleted() {
return completableFuture.isDone() | completableFuture.isCompletedExceptionally();
}
public void cancel() {
bodyHandler.cancel();
}
}
public static class ProgressTrackingBodyHandler<T> implements HttpResponse.BodyHandler<T> {
private final HttpResponse.BodyHandler<T> delegate;
private final DownloadCallback callback;
private ProgressTrackingBodySubscriber subscriber;
private boolean isCanceled = false;
public ProgressTrackingBodyHandler(HttpResponse.BodyHandler<T> delegate, DownloadCallback callback) {
this.delegate = delegate;
this.callback = callback;
}
@Override
public HttpResponse.BodySubscriber<T> apply(HttpResponse.ResponseInfo responseInfo) {
subscriber = new ProgressTrackingBodySubscriber(delegate.apply(responseInfo));
if (isCanceled) {
subscriber.cancel();
}
return subscriber;
}
public void cancel() {
isCanceled = true;
if (subscriber != null) {
subscriber.cancel();
}
}
private class ProgressTrackingBodySubscriber implements HttpResponse.BodySubscriber<T> {
private final HttpResponse.BodySubscriber<T> delegate;
private Flow.Subscription subscription;
private boolean isCanceled = false;
public ProgressTrackingBodySubscriber(HttpResponse.BodySubscriber<T> delegate) {
this.delegate = delegate;
}
@Override
public CompletionStage<T> getBody() {
return delegate.getBody();
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
if (isCanceled) {
subscription.cancel();
}
delegate.onSubscribe(subscription);
}
@Override
public void onNext(List<ByteBuffer> byteBuffers) {
long diff = 0;
for (ByteBuffer buffer : byteBuffers) {
diff += buffer.remaining();
}
if (callback != null) callback.apply(diff);
delegate.onNext(byteBuffers);
}
@Override
public void onError(Throwable throwable) {
delegate.onError(throwable);
}
@Override
public void onComplete() {
delegate.onComplete();
}
public void cancel() {
isCanceled = true;
if (subscription != null) {
subscription.cancel();
}
}
}
}
}

View file

@ -20,13 +20,15 @@ public final class JVMHelper {
public static final OperatingSystemMXBean OPERATING_SYSTEM_MXBEAN =
ManagementFactory.getOperatingSystemMXBean();
public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName());
@Deprecated
public static final int OS_BITS = getCorrectOSArch();
// System properties
public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion();
@Deprecated
public static final int OS_BITS = getCorrectOSArch();
public static final ARCH ARCH_TYPE = getArch(System.getProperty("os.arch"));
public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
public static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
// Public static fields
public static final Runtime RUNTIME = Runtime.getRuntime();
public static final ClassLoader LOADER = ClassLoader.getSystemClassLoader();
@ -44,44 +46,31 @@ public final class JVMHelper {
private JVMHelper() {
}
public enum ARCH {
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
public final String name;
ARCH(String name) {
this.name = name;
}
}
public static ARCH getArch(String arch) {
if (arch.equals("amd64") || arch.equals("x86-64") || arch.equals("x86_64")) return ARCH.X86_64;
if (arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
if (arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64;
if (arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32;
if(arch.equals("amd64") || arch.equals("x86-64") || arch.equals("x86_64")) return ARCH.X86_64;
if(arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
if(arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64;
if(arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32;
throw new InternalError(String.format("Unsupported arch '%s'", arch));
}
public static int getVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}
return Integer.parseInt(version);
//System.out.println("[DEBUG] JVMHelper 11 version");
return Runtime.version().feature();
}
public static int getBuild() {
String version = System.getProperty("java.version");
int dot;
if (version.startsWith("1.")) {
dot = version.indexOf("_");
} else {
dot = version.lastIndexOf(".");
}
if (dot != -1) {
version = version.substring(dot + 1);
}
try {
return Integer.parseInt(version);
} catch (NumberFormatException exception) {
return 0;
}
return Runtime.version().update();
}
public static void appendVars(ProcessBuilder builder, Map<String, String> vars) {
@ -98,16 +87,19 @@ public static Class<?> firstClass(String... names) throws ClassNotFoundException
throw new ClassNotFoundException(Arrays.toString(names));
}
public static void fullGC() {
RUNTIME.gc();
RUNTIME.runFinalization();
LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20);
}
public static String[] getClassPath() {
return System.getProperty("java.class.path").split(File.pathSeparator);
}
public static URL[] getClassPathURL() {
String[] cp = System.getProperty("java.class.path").split(File.pathSeparator);
URL[] list = new URL[cp.length];
@ -149,29 +141,35 @@ private static int getCorrectOSArch() {
return System.getProperty("os.arch").contains("64") ? 64 : 32;
}
public static String getEnvPropertyCaseSensitive(String name) {
return System.getenv().get(name);
}
@Deprecated
public static boolean isJVMMatchesSystemArch() {
return JVM_BITS == OS_BITS;
}
public static String jvmProperty(String name, String value) {
return String.format("-D%s=%s", name, value);
}
public static String systemToJvmProperty(String name) {
return String.format("-D%s=%s", name, System.getProperties().getProperty(name));
}
public static void addSystemPropertyToArgs(Collection<String> args, String name) {
String property = System.getProperty(name);
if (property != null)
args.add(String.format("-D%s=%s", name, property));
}
public static void verifySystemProperties(Class<?> mainClass, boolean requireSystem) {
Locale.setDefault(Locale.US);
// Verify class loader
@ -183,17 +181,6 @@ public static void verifySystemProperties(Class<?> mainClass, boolean requireSys
LogHelper.debug("Verifying JVM architecture");
}
public enum ARCH {
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
public final String name;
ARCH(String name) {
this.name = name;
}
}
public enum OS {
MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx");

View file

@ -1,60 +0,0 @@
package pro.gravit.launcher;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public final class HTTPRequest {
private static final int TIMEOUT = 10000;
private HTTPRequest() {
}
public static JsonElement jsonRequest(JsonElement request, URL url) throws IOException {
return jsonRequest(request, "POST", url);
}
public static JsonElement jsonRequest(JsonElement request, String method, URL url) throws IOException {
HttpClient client = HttpClient.newBuilder()
.build();
HttpRequest.BodyPublisher publisher;
if (request != null) {
publisher = HttpRequest.BodyPublishers.ofString(request.toString());
} else {
publisher = HttpRequest.BodyPublishers.noBody();
}
try {
HttpRequest request1 = HttpRequest.newBuilder()
.method(method, publisher)
.uri(url.toURI())
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
.timeout(Duration.ofMillis(TIMEOUT))
.build();
HttpResponse<InputStream> response = client.send(request1, HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode();
try {
return JsonParser.parseReader(IOHelper.newReader(response.body()));
} catch (Exception e) {
if (200 > statusCode || statusCode > 300) {
LogHelper.error("JsonRequest failed. Server response code %d", statusCode);
throw new IOException(e);
}
return null;
}
} catch (URISyntaxException | InterruptedException e) {
throw new IOException(e);
}
}
}

View file

@ -1,266 +0,0 @@
package pro.gravit.utils;
import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.LauncherInject;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class Downloader {
@LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning;
@LauncherInject("launcher.noHttp2")
private static boolean isNoHttp2;
protected final HttpClient client;
protected final ExecutorService executor;
protected final LinkedList<DownloadTask> tasks = new LinkedList<>();
protected CompletableFuture<Void> future;
protected Downloader(HttpClient client, ExecutorService executor) {
this.client = client;
this.executor = executor;
}
public static Downloader downloadList(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
boolean closeExecutor = false;
LogHelper.info("Download with Java 11+ HttpClient");
if (executor == null) {
executor = Executors.newWorkStealingPool(Math.min(3, threads));
closeExecutor = true;
}
Downloader downloader = newDownloader(executor);
downloader.future = downloader.downloadFiles(files, baseURL, targetDir, callback, executor, threads);
if (closeExecutor) {
ExecutorService finalExecutor = executor;
downloader.future = downloader.future.thenAccept(e -> finalExecutor.shutdownNow());
}
return downloader;
}
public static Downloader newDownloader(ExecutorService executor) {
if (executor == null) {
throw new NullPointerException();
}
HttpClient.Builder builder = HttpClient.newBuilder()
.version(isNoHttp2 ? HttpClient.Version.HTTP_1_1 : HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.executor(executor);
if (isCertificatePinning) {
try {
builder.sslContext(AsyncDownloader.makeSSLContext());
} catch (Exception e) {
throw new SecurityException(e);
}
}
HttpClient client = builder.build();
return new Downloader(client, executor);
}
public void cancel() {
for (DownloadTask task : tasks) {
if (!task.isCompleted()) {
task.cancel();
}
}
tasks.clear();
executor.shutdownNow();
}
public boolean isCanceled() {
return executor.isTerminated();
}
public CompletableFuture<Void> getFuture() {
return future;
}
public CompletableFuture<Void> downloadFiles(List<AsyncDownloader.SizedFile> files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception {
// URI scheme
URI baseUri = new URI(baseURL);
Collections.shuffle(files);
Queue<AsyncDownloader.SizedFile> queue = new ConcurrentLinkedDeque<>(files);
CompletableFuture<Void> future = new CompletableFuture<>();
AtomicInteger currentThreads = new AtomicInteger(threads);
ConsumerObject consumerObject = new ConsumerObject();
Consumer<HttpResponse<Path>> next = e -> {
if (callback != null && e != null) {
callback.onComplete(e.body());
}
AsyncDownloader.SizedFile file = queue.poll();
if (file == null) {
if (currentThreads.decrementAndGet() == 0)
future.complete(null);
return;
}
try {
DownloadTask task = sendAsync(file, baseUri, targetDir, callback);
task.completableFuture.thenAccept(consumerObject.next).exceptionally(ec -> {
future.completeExceptionally(ec);
return null;
});
} catch (Exception exception) {
LogHelper.error(exception);
future.completeExceptionally(exception);
}
};
consumerObject.next = next;
for (int i = 0; i < threads; ++i) {
next.accept(null);
}
return future;
}
protected DownloadTask sendAsync(AsyncDownloader.SizedFile file, URI baseUri, Path targetDir, DownloadCallback callback) throws Exception {
IOHelper.createParentDirs(targetDir.resolve(file.filePath));
ProgressTrackingBodyHandler<Path> bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback);
CompletableFuture<HttpResponse<Path>> future = client.sendAsync(makeHttpRequest(baseUri, file.urlPath), bodyHandler);
var ref = new Object() {
DownloadTask task = null;
};
ref.task = new DownloadTask(bodyHandler, future.thenApply((e) -> {
tasks.remove(ref.task);
return e;
}));
tasks.add(ref.task);
return ref.task;
}
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
String scheme = baseUri.getScheme();
String host = baseUri.getHost();
int port = baseUri.getPort();
if (port != -1)
host = host + ":" + port;
String path = baseUri.getPath();
return HttpRequest.newBuilder()
.GET()
.uri(new URI(scheme, host, path + filePath, "", ""))
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36")
.build();
}
protected ProgressTrackingBodyHandler<Path> makeBodyHandler(Path file, DownloadCallback callback) {
return new ProgressTrackingBodyHandler<>(HttpResponse.BodyHandlers.ofFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE), callback);
}
public interface DownloadCallback {
void apply(long fullDiff);
void onComplete(Path path);
}
private static class ConsumerObject {
Consumer<HttpResponse<Path>> next = null;
}
public static class DownloadTask {
public final ProgressTrackingBodyHandler<Path> bodyHandler;
public final CompletableFuture<HttpResponse<Path>> completableFuture;
public DownloadTask(ProgressTrackingBodyHandler<Path> bodyHandler, CompletableFuture<HttpResponse<Path>> completableFuture) {
this.bodyHandler = bodyHandler;
this.completableFuture = completableFuture;
}
public boolean isCompleted() {
return completableFuture.isDone() | completableFuture.isCompletedExceptionally();
}
public void cancel() {
bodyHandler.cancel();
}
}
public static class ProgressTrackingBodyHandler<T> implements HttpResponse.BodyHandler<T> {
private final HttpResponse.BodyHandler<T> delegate;
private final DownloadCallback callback;
private ProgressTrackingBodySubscriber subscriber;
private boolean isCanceled = false;
public ProgressTrackingBodyHandler(HttpResponse.BodyHandler<T> delegate, DownloadCallback callback) {
this.delegate = delegate;
this.callback = callback;
}
@Override
public HttpResponse.BodySubscriber<T> apply(HttpResponse.ResponseInfo responseInfo) {
subscriber = new ProgressTrackingBodySubscriber(delegate.apply(responseInfo));
if (isCanceled) {
subscriber.cancel();
}
return subscriber;
}
public void cancel() {
isCanceled = true;
if (subscriber != null) {
subscriber.cancel();
}
}
private class ProgressTrackingBodySubscriber implements HttpResponse.BodySubscriber<T> {
private final HttpResponse.BodySubscriber<T> delegate;
private Flow.Subscription subscription;
private boolean isCanceled = false;
public ProgressTrackingBodySubscriber(HttpResponse.BodySubscriber<T> delegate) {
this.delegate = delegate;
}
@Override
public CompletionStage<T> getBody() {
return delegate.getBody();
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
if (isCanceled) {
subscription.cancel();
}
delegate.onSubscribe(subscription);
}
@Override
public void onNext(List<ByteBuffer> byteBuffers) {
long diff = 0;
for (ByteBuffer buffer : byteBuffers) {
diff += buffer.remaining();
}
if (callback != null) callback.apply(diff);
delegate.onNext(byteBuffers);
}
@Override
public void onError(Throwable throwable) {
delegate.onError(throwable);
}
@Override
public void onComplete() {
delegate.onComplete();
}
public void cancel() {
isCanceled = true;
if (subscription != null) {
subscription.cancel();
}
}
}
}
}

View file

@ -1,204 +0,0 @@
package pro.gravit.utils.helper;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
public final class JVMHelper {
// MXBeans exports
public static final RuntimeMXBean RUNTIME_MXBEAN = ManagementFactory.getRuntimeMXBean();
public static final OperatingSystemMXBean OPERATING_SYSTEM_MXBEAN =
ManagementFactory.getOperatingSystemMXBean();
public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName());
// System properties
public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion();
@Deprecated
public static final int OS_BITS = getCorrectOSArch();
public static final ARCH ARCH_TYPE = getArch(System.getProperty("os.arch"));
public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
// Public static fields
public static final Runtime RUNTIME = Runtime.getRuntime();
public static final ClassLoader LOADER = ClassLoader.getSystemClassLoader();
public static final int JVM_VERSION = getVersion();
public static final int JVM_BUILD = getBuild();
static {
try {
MethodHandles.publicLookup(); // Just to initialize class
} catch (Throwable exc) {
throw new InternalError(exc);
}
}
private JVMHelper() {
}
public enum ARCH {
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
public final String name;
ARCH(String name) {
this.name = name;
}
}
public static ARCH getArch(String arch) {
if(arch.equals("amd64") || arch.equals("x86-64") || arch.equals("x86_64")) return ARCH.X86_64;
if(arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
if(arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64;
if(arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32;
throw new InternalError(String.format("Unsupported arch '%s'", arch));
}
public static int getVersion() {
//System.out.println("[DEBUG] JVMHelper 11 version");
return Runtime.version().feature();
}
public static int getBuild() {
return Runtime.version().update();
}
public static void appendVars(ProcessBuilder builder, Map<String, String> vars) {
builder.environment().putAll(vars);
}
public static Class<?> firstClass(String... names) throws ClassNotFoundException {
for (String name : names)
try {
return Class.forName(name, false, LOADER);
} catch (ClassNotFoundException ignored) {
// Expected
}
throw new ClassNotFoundException(Arrays.toString(names));
}
public static void fullGC() {
RUNTIME.gc();
RUNTIME.runFinalization();
LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20);
}
public static String[] getClassPath() {
return System.getProperty("java.class.path").split(File.pathSeparator);
}
public static URL[] getClassPathURL() {
String[] cp = System.getProperty("java.class.path").split(File.pathSeparator);
URL[] list = new URL[cp.length];
for (int i = 0; i < cp.length; i++) {
URL url = null;
try {
url = new URL(cp[i]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
list[i] = url;
}
return list;
}
public static X509Certificate[] getCertificates(Class<?> clazz) {
Object[] signers = clazz.getSigners();
if (signers == null) return null;
return Arrays.stream(signers).filter((c) -> c instanceof X509Certificate).map((c) -> (X509Certificate) c).toArray(X509Certificate[]::new);
}
public static void checkStackTrace(Class<?> mainClass) {
LogHelper.debug("Testing stacktrace");
Exception e = new Exception("Testing stacktrace");
StackTraceElement[] list = e.getStackTrace();
if (!list[list.length - 1].getClassName().equals(mainClass.getName())) {
throw new SecurityException(String.format("Invalid StackTraceElement: %s", list[list.length - 1].getClassName()));
}
}
@Deprecated
private static int getCorrectOSArch() {
// As always, mustdie must die
if (OS_TYPE == OS.MUSTDIE)
return System.getenv("ProgramFiles(x86)") == null ? 32 : 64;
// Or trust system property (maybe incorrect)
return System.getProperty("os.arch").contains("64") ? 64 : 32;
}
public static String getEnvPropertyCaseSensitive(String name) {
return System.getenv().get(name);
}
@Deprecated
public static boolean isJVMMatchesSystemArch() {
return JVM_BITS == OS_BITS;
}
public static String jvmProperty(String name, String value) {
return String.format("-D%s=%s", name, value);
}
public static String systemToJvmProperty(String name) {
return String.format("-D%s=%s", name, System.getProperties().getProperty(name));
}
public static void addSystemPropertyToArgs(Collection<String> args, String name) {
String property = System.getProperty(name);
if (property != null)
args.add(String.format("-D%s=%s", name, property));
}
public static void verifySystemProperties(Class<?> mainClass, boolean requireSystem) {
Locale.setDefault(Locale.US);
// Verify class loader
LogHelper.debug("Verifying class loader");
if (requireSystem && !mainClass.getClassLoader().equals(LOADER))
throw new SecurityException("ClassLoader should be system");
// Verify system and java architecture
LogHelper.debug("Verifying JVM architecture");
}
public enum OS {
MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx");
public final String name;
OS(String name) {
this.name = name;
}
public static OS byName(String name) {
if (name.startsWith("Windows"))
return MUSTDIE;
if (name.startsWith("Linux"))
return LINUX;
if (name.startsWith("Mac OS X"))
return MACOSX;
throw new RuntimeException(String.format("This shit is not yet supported: '%s'", name));
}
}
}

View file

@ -15,29 +15,12 @@
}
sourceSets {
java11 {
java {
srcDirs = ['src/main/java11']
}
dependencies {
java11Implementation project(':LauncherAPI')
java11Implementation files(sourceSets.main.output.classesDirs) { builtBy compileJava }
}
}
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava11Java {
sourceCompatibility = 11
targetCompatibility = 11
}
sourceCompatibility = '17'
targetCompatibility = '17'
jar {
into('META-INF/versions/11') {
from sourceSets.java11.output
}
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,

View file

@ -1,10 +1,69 @@
package pro.gravit.launcher.server.launch;
import pro.gravit.launcher.server.ServerWrapper;
import pro.gravit.utils.PublicURLClassLoader;
import pro.gravit.utils.helper.IOHelper;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class ModuleLaunch implements Launch {
@Override
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
throw new UnsupportedOperationException("Module system not supported");
URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
ClassLoader ucl = new PublicURLClassLoader(urls);
// Create Module Layer
ModuleFinder finder = ModuleFinder.of(config.moduleConf.modulePath.stream().map(Paths::get).toArray(Path[]::new));
ModuleLayer bootLayer = ModuleLayer.boot();
Configuration configuration = bootLayer.configuration()
.resolveAndBind(ModuleFinder.of(), finder, config.moduleConf.modules);
ModuleLayer.Controller controller = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(bootLayer), ucl);
ModuleLayer layer = controller.layer();
// Configure exports / opens
for(var e : config.moduleConf.exports.entrySet()) {
String[] split = e.getKey().split("\\\\");
Module source = layer.findModule(split[0]).orElseThrow();
String pkg = split[1];
Module target = layer.findModule(e.getValue()).orElseThrow();
controller.addExports(source, pkg, target);
}
for(var e : config.moduleConf.opens.entrySet()) {
String[] split = e.getKey().split("\\\\");
Module source = layer.findModule(split[0]).orElseThrow();
String pkg = split[1];
Module target = layer.findModule(e.getValue()).orElseThrow();
controller.addOpens(source, pkg, target);
}
for(var e : config.moduleConf.reads.entrySet()) {
Module source = layer.findModule(e.getKey()).orElseThrow();
Module target = layer.findModule(e.getValue()).orElseThrow();
controller.addReads(source, target);
}
Module mainModule = layer.findModule(config.moduleConf.mainModule).orElseThrow();
Module unnamed = ModuleLaunch.class.getClassLoader().getUnnamedModule();
if(unnamed != null) {
controller.addOpens(mainModule, getPackageFromClass(config.mainclass), unnamed);
}
// Start main class
ClassLoader loader = mainModule.getClassLoader();
Class<?> mainClass = Class.forName(mainclass, true, loader);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke(args);
}
private static String getPackageFromClass(String clazz) {
int index = clazz.lastIndexOf(".");
if(index >= 0) {
return clazz.substring(0, index);
}
return clazz;
}
}

View file

@ -1,69 +0,0 @@
package pro.gravit.launcher.server.launch;
import pro.gravit.launcher.server.ServerWrapper;
import pro.gravit.utils.PublicURLClassLoader;
import pro.gravit.utils.helper.IOHelper;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class ModuleLaunch implements Launch {
@Override
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
ClassLoader ucl = new PublicURLClassLoader(urls);
// Create Module Layer
ModuleFinder finder = ModuleFinder.of(config.moduleConf.modulePath.stream().map(Paths::get).toArray(Path[]::new));
ModuleLayer bootLayer = ModuleLayer.boot();
Configuration configuration = bootLayer.configuration()
.resolveAndBind(ModuleFinder.of(), finder, config.moduleConf.modules);
ModuleLayer.Controller controller = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(bootLayer), ucl);
ModuleLayer layer = controller.layer();
// Configure exports / opens
for(var e : config.moduleConf.exports.entrySet()) {
String[] split = e.getKey().split("\\\\");
Module source = layer.findModule(split[0]).orElseThrow();
String pkg = split[1];
Module target = layer.findModule(e.getValue()).orElseThrow();
controller.addExports(source, pkg, target);
}
for(var e : config.moduleConf.opens.entrySet()) {
String[] split = e.getKey().split("\\\\");
Module source = layer.findModule(split[0]).orElseThrow();
String pkg = split[1];
Module target = layer.findModule(e.getValue()).orElseThrow();
controller.addOpens(source, pkg, target);
}
for(var e : config.moduleConf.reads.entrySet()) {
Module source = layer.findModule(e.getKey()).orElseThrow();
Module target = layer.findModule(e.getValue()).orElseThrow();
controller.addReads(source, target);
}
Module mainModule = layer.findModule(config.moduleConf.mainModule).orElseThrow();
Module unnamed = ModuleLaunch.class.getClassLoader().getUnnamedModule();
if(unnamed != null) {
controller.addOpens(mainModule, getPackageFromClass(config.mainclass), unnamed);
}
// Start main class
ClassLoader loader = mainModule.getClassLoader();
Class<?> mainClass = Class.forName(mainclass, true, loader);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke(args);
}
private static String getPackageFromClass(String clazz) {
int index = clazz.lastIndexOf(".");
if(index >= 0) {
return clazz.substring(0, index);
}
return clazz;
}
}

View file

@ -1,5 +1,5 @@
plugins {
id 'com.github.johnrengelman.shadow' version '5.2.0' apply false
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id 'maven-publish'
id 'signing'
id 'org.openjfx.javafxplugin' version '0.0.10' apply false

@ -1 +1 @@
Subproject commit aba8a880bd644c211f6f6d6fdceedd21adf66ca6
Subproject commit b20ea06d769fc22dcc47db5a1afcda3bc1e996b5