diff --git a/LaunchServer/src/main/resources/experimental-build.json b/LaunchServer/src/main/resources/experimental-build.json index ab148d00..656fe6cc 100644 --- a/LaunchServer/src/main/resources/experimental-build.json +++ b/LaunchServer/src/main/resources/experimental-build.json @@ -1,4 +1,4 @@ { - "features": [], + "features": ["java17"], "info": [] } \ No newline at end of file diff --git a/Launcher/build.gradle b/Launcher/build.gradle index e8ca4f7f..70c6e99a 100644 --- a/Launcher/build.gradle +++ b/Launcher/build.gradle @@ -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 diff --git a/LauncherAPI/build.gradle b/LauncherAPI/build.gradle index 64e576c5..413a4c21 100644 --- a/LauncherAPI/build.gradle +++ b/LauncherAPI/build.gradle @@ -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') diff --git a/LauncherCore/build.gradle b/LauncherCore/build.gradle index 4594ba7c..244835ac 100644 --- a/LauncherCore/build.gradle +++ b/LauncherCore/build.gradle @@ -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 diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/HTTPRequest.java b/LauncherCore/src/main/java/pro/gravit/launcher/HTTPRequest.java index d33adae1..eef69ae4 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/HTTPRequest.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/HTTPRequest.java @@ -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 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); } } } diff --git a/LauncherCore/src/main/java/pro/gravit/utils/Downloader.java b/LauncherCore/src/main/java/pro/gravit/utils/Downloader.java index b36da07f..7f3445cf 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/Downloader.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/Downloader.java @@ -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 future; - private final AsyncDownloader asyncDownloader; - private Downloader(CompletableFuture 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 tasks = new LinkedList<>(); + protected CompletableFuture future; + protected Downloader(HttpClient client, ExecutorService executor) { + this.client = client; + this.executor = executor; } public static Downloader downloadList(List 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.sortFiles(files, threads); - CompletableFuture 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 getFuture() { return future; } - public void cancel() { - this.asyncDownloader.isClosed = true; + public CompletableFuture downloadFiles(List files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception { + // URI scheme + URI baseUri = new URI(baseURL); + Collections.shuffle(files); + Queue queue = new ConcurrentLinkedDeque<>(files); + CompletableFuture future = new CompletableFuture<>(); + AtomicInteger currentThreads = new AtomicInteger(threads); + ConsumerObject consumerObject = new ConsumerObject(); + Consumer> 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 bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback); + CompletableFuture> 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 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> next = null; + } + + public static class DownloadTask { + public final ProgressTrackingBodyHandler bodyHandler; + public final CompletableFuture> completableFuture; + + public DownloadTask(ProgressTrackingBodyHandler bodyHandler, CompletableFuture> completableFuture) { + this.bodyHandler = bodyHandler; + this.completableFuture = completableFuture; + } + + public boolean isCompleted() { + return completableFuture.isDone() | completableFuture.isCompletedExceptionally(); + } + + public void cancel() { + bodyHandler.cancel(); + } + } + + public static class ProgressTrackingBodyHandler implements HttpResponse.BodyHandler { + private final HttpResponse.BodyHandler delegate; + private final DownloadCallback callback; + private ProgressTrackingBodySubscriber subscriber; + private boolean isCanceled = false; + + public ProgressTrackingBodyHandler(HttpResponse.BodyHandler delegate, DownloadCallback callback) { + this.delegate = delegate; + this.callback = callback; + } + + @Override + public HttpResponse.BodySubscriber 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 { + private final HttpResponse.BodySubscriber delegate; + private Flow.Subscription subscription; + private boolean isCanceled = false; + + public ProgressTrackingBodySubscriber(HttpResponse.BodySubscriber delegate) { + this.delegate = delegate; + } + + @Override + public CompletionStage 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 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(); + } + } + } + } } diff --git a/LauncherCore/src/main/java/pro/gravit/utils/helper/JVMHelper.java b/LauncherCore/src/main/java/pro/gravit/utils/helper/JVMHelper.java index 43acfbe7..756e7fc3 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/helper/JVMHelper.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/helper/JVMHelper.java @@ -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 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 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"); diff --git a/LauncherCore/src/main/java11/pro/gravit/launcher/HTTPRequest.java b/LauncherCore/src/main/java11/pro/gravit/launcher/HTTPRequest.java deleted file mode 100644 index eef69ae4..00000000 --- a/LauncherCore/src/main/java11/pro/gravit/launcher/HTTPRequest.java +++ /dev/null @@ -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 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); - } - } -} diff --git a/LauncherCore/src/main/java11/pro/gravit/utils/Downloader.java b/LauncherCore/src/main/java11/pro/gravit/utils/Downloader.java deleted file mode 100644 index 7f3445cf..00000000 --- a/LauncherCore/src/main/java11/pro/gravit/utils/Downloader.java +++ /dev/null @@ -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 tasks = new LinkedList<>(); - protected CompletableFuture future; - protected Downloader(HttpClient client, ExecutorService executor) { - this.client = client; - this.executor = executor; - } - - public static Downloader downloadList(List 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 getFuture() { - return future; - } - - public CompletableFuture downloadFiles(List files, String baseURL, Path targetDir, DownloadCallback callback, ExecutorService executor, int threads) throws Exception { - // URI scheme - URI baseUri = new URI(baseURL); - Collections.shuffle(files); - Queue queue = new ConcurrentLinkedDeque<>(files); - CompletableFuture future = new CompletableFuture<>(); - AtomicInteger currentThreads = new AtomicInteger(threads); - ConsumerObject consumerObject = new ConsumerObject(); - Consumer> 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 bodyHandler = makeBodyHandler(targetDir.resolve(file.filePath), callback); - CompletableFuture> 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 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> next = null; - } - - public static class DownloadTask { - public final ProgressTrackingBodyHandler bodyHandler; - public final CompletableFuture> completableFuture; - - public DownloadTask(ProgressTrackingBodyHandler bodyHandler, CompletableFuture> completableFuture) { - this.bodyHandler = bodyHandler; - this.completableFuture = completableFuture; - } - - public boolean isCompleted() { - return completableFuture.isDone() | completableFuture.isCompletedExceptionally(); - } - - public void cancel() { - bodyHandler.cancel(); - } - } - - public static class ProgressTrackingBodyHandler implements HttpResponse.BodyHandler { - private final HttpResponse.BodyHandler delegate; - private final DownloadCallback callback; - private ProgressTrackingBodySubscriber subscriber; - private boolean isCanceled = false; - - public ProgressTrackingBodyHandler(HttpResponse.BodyHandler delegate, DownloadCallback callback) { - this.delegate = delegate; - this.callback = callback; - } - - @Override - public HttpResponse.BodySubscriber 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 { - private final HttpResponse.BodySubscriber delegate; - private Flow.Subscription subscription; - private boolean isCanceled = false; - - public ProgressTrackingBodySubscriber(HttpResponse.BodySubscriber delegate) { - this.delegate = delegate; - } - - @Override - public CompletionStage 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 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(); - } - } - } - } -} diff --git a/LauncherCore/src/main/java11/pro/gravit/utils/helper/JVMHelper.java b/LauncherCore/src/main/java11/pro/gravit/utils/helper/JVMHelper.java deleted file mode 100644 index 756e7fc3..00000000 --- a/LauncherCore/src/main/java11/pro/gravit/utils/helper/JVMHelper.java +++ /dev/null @@ -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 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 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)); - } - } - -} diff --git a/ServerWrapper/build.gradle b/ServerWrapper/build.gradle index ff62dd0a..ad576237 100644 --- a/ServerWrapper/build.gradle +++ b/ServerWrapper/build.gradle @@ -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, diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java index cc0707ee..53cbfa7a 100644 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java @@ -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; } } diff --git a/ServerWrapper/src/main/java11/pro/gravit/launcher/server/launch/ModuleLaunch.java b/ServerWrapper/src/main/java11/pro/gravit/launcher/server/launch/ModuleLaunch.java deleted file mode 100644 index 53cbfa7a..00000000 --- a/ServerWrapper/src/main/java11/pro/gravit/launcher/server/launch/ModuleLaunch.java +++ /dev/null @@ -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; - } -} diff --git a/build.gradle b/build.gradle index 4745aaaf..8b09b683 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/modules b/modules index aba8a880..b20ea06d 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit aba8a880bd644c211f6f6d6fdceedd21adf66ca6 +Subproject commit b20ea06d769fc22dcc47db5a1afcda3bc1e996b5