Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.base/share/classes/sun/launcher/LauncherHelper.java
67760 views
1
/*
2
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package sun.launcher;
27
28
/*
29
*
30
* <p><b>This is NOT part of any API supported by Sun Microsystems.
31
* If you write code that depends on this, you do so at your own
32
* risk. This code and its internal interfaces are subject to change
33
* or deletion without notice.</b>
34
*
35
*/
36
37
/**
38
* A utility package for the java(1), javaw(1) launchers.
39
* The following are helper methods that the native launcher uses
40
* to perform checks etc. using JNI, see src/share/bin/java.c
41
*/
42
import java.io.File;
43
import java.io.IOException;
44
import java.io.PrintStream;
45
import java.io.UnsupportedEncodingException;
46
import java.lang.module.Configuration;
47
import java.lang.module.ModuleDescriptor;
48
import java.lang.module.ModuleDescriptor.Exports;
49
import java.lang.module.ModuleDescriptor.Opens;
50
import java.lang.module.ModuleDescriptor.Provides;
51
import java.lang.module.ModuleDescriptor.Requires;
52
import java.lang.module.ModuleFinder;
53
import java.lang.module.ModuleReference;
54
import java.lang.module.ResolvedModule;
55
import java.lang.reflect.InvocationTargetException;
56
import java.lang.reflect.Method;
57
import java.lang.reflect.Modifier;
58
import java.math.BigDecimal;
59
import java.math.RoundingMode;
60
import java.net.URI;
61
import java.nio.charset.Charset;
62
import java.nio.file.DirectoryStream;
63
import java.nio.file.Files;
64
import java.nio.file.Path;
65
import java.text.MessageFormat;
66
import java.text.Normalizer;
67
import java.util.ArrayList;
68
import java.util.Collections;
69
import java.util.Comparator;
70
import java.util.Iterator;
71
import java.util.List;
72
import java.util.Locale;
73
import java.util.Locale.Category;
74
import java.util.Optional;
75
import java.util.Properties;
76
import java.util.ResourceBundle;
77
import java.util.Set;
78
import java.util.TreeSet;
79
import java.util.jar.Attributes;
80
import java.util.jar.JarFile;
81
import java.util.jar.Manifest;
82
import java.util.stream.Collectors;
83
import java.util.stream.Stream;
84
85
import jdk.internal.misc.VM;
86
import jdk.internal.module.ModuleBootstrap;
87
import jdk.internal.module.Modules;
88
import jdk.internal.platform.Container;
89
import jdk.internal.platform.Metrics;
90
91
92
public final class LauncherHelper {
93
94
// No instantiation
95
private LauncherHelper() {}
96
97
// used to identify JavaFX applications
98
private static final String JAVAFX_APPLICATION_MARKER =
99
"JavaFX-Application-Class";
100
private static final String JAVAFX_APPLICATION_CLASS_NAME =
101
"javafx.application.Application";
102
private static final String JAVAFX_FXHELPER_CLASS_NAME_SUFFIX =
103
"sun.launcher.LauncherHelper$FXHelper";
104
private static final String LAUNCHER_AGENT_CLASS = "Launcher-Agent-Class";
105
private static final String MAIN_CLASS = "Main-Class";
106
private static final String ADD_EXPORTS = "Add-Exports";
107
private static final String ADD_OPENS = "Add-Opens";
108
109
private static StringBuilder outBuf = new StringBuilder();
110
111
private static final String INDENT = " ";
112
private static final String VM_SETTINGS = "VM settings:";
113
private static final String PROP_SETTINGS = "Property settings:";
114
private static final String LOCALE_SETTINGS = "Locale settings:";
115
116
// sync with java.c and jdk.internal.misc.VM
117
private static final String diagprop = "sun.java.launcher.diag";
118
static final boolean trace = VM.getSavedProperty(diagprop) != null;
119
120
private static final String defaultBundleName =
121
"sun.launcher.resources.launcher";
122
123
private static class ResourceBundleHolder {
124
private static final ResourceBundle RB =
125
ResourceBundle.getBundle(defaultBundleName);
126
}
127
private static PrintStream ostream;
128
private static Class<?> appClass; // application class, for GUI/reporting purposes
129
130
/*
131
* A method called by the launcher to print out the standard settings,
132
* by default -XshowSettings is equivalent to -XshowSettings:all,
133
* Specific information may be gotten by using suboptions with possible
134
* values vm, properties and locale.
135
*
136
* printToStderr: choose between stdout and stderr
137
*
138
* optionFlag: specifies which options to print default is all other
139
* possible values are vm, properties, locale.
140
*
141
* initialHeapSize: in bytes, as set by the launcher, a zero-value indicates
142
* this code should determine this value, using a suitable method or
143
* the line could be omitted.
144
*
145
* maxHeapSize: in bytes, as set by the launcher, a zero-value indicates
146
* this code should determine this value, using a suitable method.
147
*
148
* stackSize: in bytes, as set by the launcher, a zero-value indicates
149
* this code determine this value, using a suitable method or omit the
150
* line entirely.
151
*/
152
@SuppressWarnings("fallthrough")
153
static void showSettings(boolean printToStderr, String optionFlag,
154
long initialHeapSize, long maxHeapSize, long stackSize) {
155
156
initOutput(printToStderr);
157
String opts[] = optionFlag.split(":");
158
String optStr = (opts.length > 1 && opts[1] != null)
159
? opts[1].trim()
160
: "all";
161
switch (optStr) {
162
case "vm":
163
printVmSettings(initialHeapSize, maxHeapSize, stackSize);
164
break;
165
case "properties":
166
printProperties();
167
break;
168
case "locale":
169
printLocale();
170
break;
171
case "system":
172
if (System.getProperty("os.name").contains("Linux")) {
173
printSystemMetrics();
174
break;
175
}
176
default:
177
printVmSettings(initialHeapSize, maxHeapSize, stackSize);
178
printProperties();
179
printLocale();
180
if (System.getProperty("os.name").contains("Linux")) {
181
printSystemMetrics();
182
}
183
break;
184
}
185
}
186
187
/*
188
* prints the main vm settings subopt/section
189
*/
190
private static void printVmSettings(
191
long initialHeapSize, long maxHeapSize,
192
long stackSize) {
193
194
ostream.println(VM_SETTINGS);
195
if (stackSize != 0L) {
196
ostream.println(INDENT + "Stack Size: " +
197
SizePrefix.scaleValue(stackSize));
198
}
199
if (initialHeapSize != 0L) {
200
ostream.println(INDENT + "Min. Heap Size: " +
201
SizePrefix.scaleValue(initialHeapSize));
202
}
203
if (maxHeapSize != 0L) {
204
ostream.println(INDENT + "Max. Heap Size: " +
205
SizePrefix.scaleValue(maxHeapSize));
206
} else {
207
ostream.println(INDENT + "Max. Heap Size (Estimated): "
208
+ SizePrefix.scaleValue(Runtime.getRuntime().maxMemory()));
209
}
210
ostream.println(INDENT + "Using VM: "
211
+ System.getProperty("java.vm.name"));
212
ostream.println();
213
}
214
215
/*
216
* prints the properties subopt/section
217
*/
218
private static void printProperties() {
219
Properties p = System.getProperties();
220
ostream.println(PROP_SETTINGS);
221
List<String> sortedPropertyKeys = new ArrayList<>();
222
sortedPropertyKeys.addAll(p.stringPropertyNames());
223
Collections.sort(sortedPropertyKeys);
224
for (String x : sortedPropertyKeys) {
225
printPropertyValue(x, p.getProperty(x));
226
}
227
ostream.println();
228
}
229
230
private static boolean isPath(String key) {
231
return key.endsWith(".dirs") || key.endsWith(".path");
232
}
233
234
private static void printPropertyValue(String key, String value) {
235
ostream.print(INDENT + key + " = ");
236
if (key.equals("line.separator")) {
237
for (byte b : value.getBytes()) {
238
switch (b) {
239
case 0xd:
240
ostream.print("\\r ");
241
break;
242
case 0xa:
243
ostream.print("\\n ");
244
break;
245
default:
246
// print any bizzare line separators in hex, but really
247
// shouldn't happen.
248
ostream.printf("0x%02X", b & 0xff);
249
break;
250
}
251
}
252
ostream.println();
253
return;
254
}
255
if (!isPath(key)) {
256
ostream.println(value);
257
return;
258
}
259
String[] values = value.split(System.getProperty("path.separator"));
260
boolean first = true;
261
for (String s : values) {
262
if (first) { // first line treated specially
263
ostream.println(s);
264
first = false;
265
} else { // following lines prefix with indents
266
ostream.println(INDENT + INDENT + s);
267
}
268
}
269
}
270
271
/*
272
* prints the locale subopt/section
273
*/
274
private static void printLocale() {
275
Locale locale = Locale.getDefault();
276
ostream.println(LOCALE_SETTINGS);
277
ostream.println(INDENT + "default locale = " +
278
locale.getDisplayName());
279
ostream.println(INDENT + "default display locale = " +
280
Locale.getDefault(Category.DISPLAY).getDisplayName());
281
ostream.println(INDENT + "default format locale = " +
282
Locale.getDefault(Category.FORMAT).getDisplayName());
283
printLocales();
284
ostream.println();
285
}
286
287
private static void printLocales() {
288
Locale[] tlocales = Locale.getAvailableLocales();
289
final int len = tlocales == null ? 0 : tlocales.length;
290
if (len < 1 ) {
291
return;
292
}
293
// Locale does not implement Comparable so we convert it to String
294
// and sort it for pretty printing.
295
Set<String> sortedSet = new TreeSet<>();
296
for (Locale l : tlocales) {
297
sortedSet.add(l.toString());
298
}
299
300
ostream.print(INDENT + "available locales = ");
301
Iterator<String> iter = sortedSet.iterator();
302
final int last = len - 1;
303
for (int i = 0 ; iter.hasNext() ; i++) {
304
String s = iter.next();
305
ostream.print(s);
306
if (i != last) {
307
ostream.print(", ");
308
}
309
// print columns of 8
310
if ((i + 1) % 8 == 0) {
311
ostream.println();
312
ostream.print(INDENT + INDENT);
313
}
314
}
315
}
316
317
public static void printSystemMetrics() {
318
Metrics c = Container.metrics();
319
320
ostream.println("Operating System Metrics:");
321
322
if (c == null) {
323
ostream.println(INDENT + "No metrics available for this platform");
324
return;
325
}
326
327
final long longRetvalNotSupported = -2;
328
329
ostream.println(INDENT + "Provider: " + c.getProvider());
330
ostream.println(INDENT + "Effective CPU Count: " + c.getEffectiveCpuCount());
331
ostream.println(formatCpuVal(c.getCpuPeriod(), INDENT + "CPU Period: ", longRetvalNotSupported));
332
ostream.println(formatCpuVal(c.getCpuQuota(), INDENT + "CPU Quota: ", longRetvalNotSupported));
333
ostream.println(formatCpuVal(c.getCpuShares(), INDENT + "CPU Shares: ", longRetvalNotSupported));
334
335
int cpus[] = c.getCpuSetCpus();
336
if (cpus != null) {
337
ostream.println(INDENT + "List of Processors, "
338
+ cpus.length + " total: ");
339
340
ostream.print(INDENT);
341
for (int i = 0; i < cpus.length; i++) {
342
ostream.print(cpus[i] + " ");
343
}
344
if (cpus.length > 0) {
345
ostream.println("");
346
}
347
} else {
348
ostream.println(INDENT + "List of Processors: N/A");
349
}
350
351
cpus = c.getEffectiveCpuSetCpus();
352
if (cpus != null) {
353
ostream.println(INDENT + "List of Effective Processors, "
354
+ cpus.length + " total: ");
355
356
ostream.print(INDENT);
357
for (int i = 0; i < cpus.length; i++) {
358
ostream.print(cpus[i] + " ");
359
}
360
if (cpus.length > 0) {
361
ostream.println("");
362
}
363
} else {
364
ostream.println(INDENT + "List of Effective Processors: N/A");
365
}
366
367
int mems[] = c.getCpuSetMems();
368
if (mems != null) {
369
ostream.println(INDENT + "List of Memory Nodes, "
370
+ mems.length + " total: ");
371
372
ostream.print(INDENT);
373
for (int i = 0; i < mems.length; i++) {
374
ostream.print(mems[i] + " ");
375
}
376
if (mems.length > 0) {
377
ostream.println("");
378
}
379
} else {
380
ostream.println(INDENT + "List of Memory Nodes: N/A");
381
}
382
383
mems = c.getEffectiveCpuSetMems();
384
if (mems != null) {
385
ostream.println(INDENT + "List of Available Memory Nodes, "
386
+ mems.length + " total: ");
387
388
ostream.print(INDENT);
389
for (int i = 0; i < mems.length; i++) {
390
ostream.print(mems[i] + " ");
391
}
392
if (mems.length > 0) {
393
ostream.println("");
394
}
395
} else {
396
ostream.println(INDENT + "List of Available Memory Nodes: N/A");
397
}
398
399
long limit = c.getMemoryLimit();
400
ostream.println(formatLimitString(limit, INDENT + "Memory Limit: ", longRetvalNotSupported));
401
402
limit = c.getMemorySoftLimit();
403
ostream.println(formatLimitString(limit, INDENT + "Memory Soft Limit: ", longRetvalNotSupported));
404
405
limit = c.getMemoryAndSwapLimit();
406
ostream.println(formatLimitString(limit, INDENT + "Memory & Swap Limit: ", longRetvalNotSupported));
407
408
limit = c.getPidsMax();
409
ostream.println(formatLimitString(limit, INDENT + "Maximum Processes Limit: ",
410
longRetvalNotSupported, false));
411
ostream.println("");
412
}
413
414
private static String formatLimitString(long limit, String prefix, long unavailable) {
415
return formatLimitString(limit, prefix, unavailable, true);
416
}
417
418
private static String formatLimitString(long limit, String prefix, long unavailable, boolean scale) {
419
if (limit >= 0) {
420
if (scale) {
421
return prefix + SizePrefix.scaleValue(limit);
422
} else {
423
return prefix + limit;
424
}
425
} else if (limit == unavailable) {
426
return prefix + "N/A";
427
} else {
428
return prefix + "Unlimited";
429
}
430
}
431
432
private static String formatCpuVal(long cpuVal, String prefix, long unavailable) {
433
if (cpuVal >= 0) {
434
return prefix + cpuVal + "us";
435
} else if (cpuVal == unavailable) {
436
return prefix + "N/A";
437
} else {
438
return prefix + cpuVal;
439
}
440
}
441
442
private enum SizePrefix {
443
444
KILO(1024, "K"),
445
MEGA(1024 * 1024, "M"),
446
GIGA(1024 * 1024 * 1024, "G"),
447
TERA(1024L * 1024L * 1024L * 1024L, "T");
448
long size;
449
String abbrev;
450
451
SizePrefix(long size, String abbrev) {
452
this.size = size;
453
this.abbrev = abbrev;
454
}
455
456
private static String scale(long v, SizePrefix prefix) {
457
return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size),
458
2, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev;
459
}
460
/*
461
* scale the incoming values to a human readable form, represented as
462
* K, M, G and T, see java.c parse_size for the scaled values and
463
* suffixes. The lowest possible scaled value is Kilo.
464
*/
465
static String scaleValue(long v) {
466
if (v < MEGA.size) {
467
return scale(v, KILO);
468
} else if (v < GIGA.size) {
469
return scale(v, MEGA);
470
} else if (v < TERA.size) {
471
return scale(v, GIGA);
472
} else {
473
return scale(v, TERA);
474
}
475
}
476
}
477
478
/**
479
* A private helper method to get a localized message and also
480
* apply any arguments that we might pass.
481
*/
482
private static String getLocalizedMessage(String key, Object... args) {
483
String msg = ResourceBundleHolder.RB.getString(key);
484
return (args != null) ? MessageFormat.format(msg, args) : msg;
485
}
486
487
/**
488
* The java -help message is split into 3 parts, an invariant, followed
489
* by a set of platform dependent variant messages, finally an invariant
490
* set of lines.
491
* This method initializes the help message for the first time, and also
492
* assembles the invariant header part of the message.
493
*/
494
static void initHelpMessage(String progname) {
495
outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header",
496
(progname == null) ? "java" : progname ));
497
}
498
499
/**
500
* Appends the vm selection messages to the header, already created.
501
* initHelpSystem must already be called.
502
*/
503
static void appendVmSelectMessage(String vm1, String vm2) {
504
outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect",
505
vm1, vm2));
506
}
507
508
/**
509
* Appends the vm synoym message to the header, already created.
510
* initHelpSystem must be called before using this method.
511
*/
512
static void appendVmSynonymMessage(String vm1, String vm2) {
513
outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot",
514
vm1, vm2));
515
}
516
517
/**
518
* Appends the last invariant part to the previously created messages,
519
* and finishes up the printing to the desired output stream.
520
* initHelpSystem must be called before using this method.
521
*/
522
static void printHelpMessage(boolean printToStderr) {
523
initOutput(printToStderr);
524
outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",
525
File.pathSeparator));
526
ostream.println(outBuf.toString());
527
}
528
529
/**
530
* Prints the Xusage text to the desired output stream.
531
*/
532
static void printXUsageMessage(boolean printToStderr) {
533
initOutput(printToStderr);
534
ostream.println(getLocalizedMessage("java.launcher.X.usage",
535
File.pathSeparator));
536
if (System.getProperty("os.name").contains("OS X")) {
537
ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage",
538
File.pathSeparator));
539
}
540
}
541
542
static void initOutput(boolean printToStderr) {
543
ostream = (printToStderr) ? System.err : System.out;
544
}
545
546
static void initOutput(PrintStream ps) {
547
ostream = ps;
548
}
549
550
static String getMainClassFromJar(String jarname) {
551
String mainValue;
552
try (JarFile jarFile = new JarFile(jarname)) {
553
Manifest manifest = jarFile.getManifest();
554
if (manifest == null) {
555
abort(null, "java.launcher.jar.error2", jarname);
556
}
557
Attributes mainAttrs = manifest.getMainAttributes();
558
if (mainAttrs == null) {
559
abort(null, "java.launcher.jar.error3", jarname);
560
}
561
562
// Main-Class
563
mainValue = mainAttrs.getValue(MAIN_CLASS);
564
if (mainValue == null) {
565
abort(null, "java.launcher.jar.error3", jarname);
566
}
567
568
// Launcher-Agent-Class (only check for this when Main-Class present)
569
String agentClass = mainAttrs.getValue(LAUNCHER_AGENT_CLASS);
570
if (agentClass != null) {
571
ModuleLayer.boot().findModule("java.instrument").ifPresent(m -> {
572
try {
573
String cn = "sun.instrument.InstrumentationImpl";
574
Class<?> clazz = Class.forName(cn, false, null);
575
Method loadAgent = clazz.getMethod("loadAgent", String.class);
576
loadAgent.invoke(null, jarname);
577
} catch (Throwable e) {
578
if (e instanceof InvocationTargetException) e = e.getCause();
579
abort(e, "java.launcher.jar.error4", jarname);
580
}
581
});
582
}
583
584
// Add-Exports and Add-Opens
585
String exports = mainAttrs.getValue(ADD_EXPORTS);
586
if (exports != null) {
587
addExportsOrOpens(exports, false);
588
}
589
String opens = mainAttrs.getValue(ADD_OPENS);
590
if (opens != null) {
591
addExportsOrOpens(opens, true);
592
}
593
594
/*
595
* Hand off to FXHelper if it detects a JavaFX application
596
* This must be done after ensuring a Main-Class entry
597
* exists to enforce compliance with the jar specification
598
*/
599
if (mainAttrs.containsKey(
600
new Attributes.Name(JAVAFX_APPLICATION_MARKER))) {
601
FXHelper.setFXLaunchParameters(jarname, LM_JAR);
602
return FXHelper.class.getName();
603
}
604
605
return mainValue.trim();
606
} catch (IOException ioe) {
607
abort(ioe, "java.launcher.jar.error1", jarname);
608
}
609
return null;
610
}
611
612
/**
613
* Process the Add-Exports or Add-Opens value. The value is
614
* {@code <module>/<package> ( <module>/<package>)*}.
615
*/
616
static void addExportsOrOpens(String value, boolean open) {
617
for (String moduleAndPackage : value.split(" ")) {
618
String[] s = moduleAndPackage.trim().split("/");
619
if (s.length == 2) {
620
String mn = s[0];
621
String pn = s[1];
622
ModuleLayer.boot()
623
.findModule(mn)
624
.filter(m -> m.getDescriptor().packages().contains(pn))
625
.ifPresent(m -> {
626
if (open) {
627
Modules.addOpensToAllUnnamed(m, pn);
628
} else {
629
Modules.addExportsToAllUnnamed(m, pn);
630
}
631
});
632
}
633
}
634
}
635
636
// From src/share/bin/java.c:
637
// enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }
638
639
private static final int LM_UNKNOWN = 0;
640
private static final int LM_CLASS = 1;
641
private static final int LM_JAR = 2;
642
private static final int LM_MODULE = 3;
643
private static final int LM_SOURCE = 4;
644
645
static void abort(Throwable t, String msgKey, Object... args) {
646
if (msgKey != null) {
647
ostream.println(getLocalizedMessage(msgKey, args));
648
}
649
if (trace) {
650
if (t != null) {
651
t.printStackTrace();
652
} else {
653
Thread.dumpStack();
654
}
655
}
656
System.exit(1);
657
}
658
659
/**
660
* This method:
661
* 1. Loads the main class from the module or class path
662
* 2. Checks the public static void main method.
663
* 3. If the main class extends FX Application then call on FXHelper to
664
* perform the launch.
665
*
666
* @param printToStderr if set, all output will be routed to stderr
667
* @param mode LaunchMode as determined by the arguments passed on the
668
* command line
669
* @param what the module name[/class], JAR file, or the main class
670
* depending on the mode
671
*
672
* @return the application's main class
673
*/
674
@SuppressWarnings("fallthrough")
675
public static Class<?> checkAndLoadMain(boolean printToStderr,
676
int mode,
677
String what) {
678
initOutput(printToStderr);
679
680
Class<?> mainClass = null;
681
switch (mode) {
682
case LM_MODULE: case LM_SOURCE:
683
mainClass = loadModuleMainClass(what);
684
break;
685
default:
686
mainClass = loadMainClass(mode, what);
687
break;
688
}
689
690
// record the real main class for UI purposes
691
// neither method above can return null, they will abort()
692
appClass = mainClass;
693
694
/*
695
* Check if FXHelper can launch it using the FX launcher. In an FX app,
696
* the main class may or may not have a main method, so do this before
697
* validating the main class.
698
*/
699
if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) ||
700
doesExtendFXApplication(mainClass)) {
701
// Will abort() if there are problems with FX runtime
702
FXHelper.setFXLaunchParameters(what, mode);
703
mainClass = FXHelper.class;
704
}
705
706
validateMainClass(mainClass);
707
return mainClass;
708
}
709
710
/**
711
* Returns the main class for a module. The query is either a module name
712
* or module-name/main-class. For the former then the module's main class
713
* is obtained from the module descriptor (MainClass attribute).
714
*/
715
private static Class<?> loadModuleMainClass(String what) {
716
int i = what.indexOf('/');
717
String mainModule;
718
String mainClass;
719
if (i == -1) {
720
mainModule = what;
721
mainClass = null;
722
} else {
723
mainModule = what.substring(0, i);
724
mainClass = what.substring(i+1);
725
}
726
727
// main module is in the boot layer
728
ModuleLayer layer = ModuleLayer.boot();
729
Optional<Module> om = layer.findModule(mainModule);
730
if (!om.isPresent()) {
731
// should not happen
732
throw new InternalError("Module " + mainModule + " not in boot Layer");
733
}
734
Module m = om.get();
735
736
// get main class
737
if (mainClass == null) {
738
Optional<String> omc = m.getDescriptor().mainClass();
739
if (!omc.isPresent()) {
740
abort(null, "java.launcher.module.error1", mainModule);
741
}
742
mainClass = omc.get();
743
}
744
745
// load the class from the module
746
Class<?> c = null;
747
try {
748
c = Class.forName(m, mainClass);
749
if (c == null && System.getProperty("os.name", "").contains("OS X")
750
&& Normalizer.isNormalized(mainClass, Normalizer.Form.NFD)) {
751
752
String cn = Normalizer.normalize(mainClass, Normalizer.Form.NFC);
753
c = Class.forName(m, cn);
754
}
755
} catch (LinkageError le) {
756
abort(null, "java.launcher.module.error3", mainClass, m.getName(),
757
le.getClass().getName() + ": " + le.getLocalizedMessage());
758
}
759
if (c == null) {
760
abort(null, "java.launcher.module.error2", mainClass, mainModule);
761
}
762
763
System.setProperty("jdk.module.main.class", c.getName());
764
return c;
765
}
766
767
/**
768
* Loads the main class from the class path (LM_CLASS or LM_JAR).
769
*/
770
private static Class<?> loadMainClass(int mode, String what) {
771
// get the class name
772
String cn;
773
switch (mode) {
774
case LM_CLASS:
775
cn = what;
776
break;
777
case LM_JAR:
778
cn = getMainClassFromJar(what);
779
break;
780
default:
781
// should never happen
782
throw new InternalError("" + mode + ": Unknown launch mode");
783
}
784
785
// load the main class
786
cn = cn.replace('/', '.');
787
Class<?> mainClass = null;
788
ClassLoader scl = ClassLoader.getSystemClassLoader();
789
try {
790
try {
791
mainClass = Class.forName(cn, false, scl);
792
} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
793
if (System.getProperty("os.name", "").contains("OS X")
794
&& Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {
795
try {
796
// On Mac OS X since all names with diacritical marks are
797
// given as decomposed it is possible that main class name
798
// comes incorrectly from the command line and we have
799
// to re-compose it
800
String ncn = Normalizer.normalize(cn, Normalizer.Form.NFC);
801
mainClass = Class.forName(ncn, false, scl);
802
} catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {
803
abort(cnfe1, "java.launcher.cls.error1", cn,
804
cnfe1.getClass().getCanonicalName(), cnfe1.getMessage());
805
}
806
} else {
807
abort(cnfe, "java.launcher.cls.error1", cn,
808
cnfe.getClass().getCanonicalName(), cnfe.getMessage());
809
}
810
}
811
} catch (LinkageError le) {
812
abort(le, "java.launcher.cls.error6", cn,
813
le.getClass().getName() + ": " + le.getLocalizedMessage());
814
}
815
return mainClass;
816
}
817
818
/*
819
* Accessor method called by the launcher after getting the main class via
820
* checkAndLoadMain(). The "application class" is the class that is finally
821
* executed to start the application and in this case is used to report
822
* the correct application name, typically for UI purposes.
823
*/
824
public static Class<?> getApplicationClass() {
825
return appClass;
826
}
827
828
/*
829
* Check if the given class is a JavaFX Application class. This is done
830
* in a way that does not cause the Application class to load or throw
831
* ClassNotFoundException if the JavaFX runtime is not available.
832
*/
833
private static boolean doesExtendFXApplication(Class<?> mainClass) {
834
for (Class<?> sc = mainClass.getSuperclass(); sc != null;
835
sc = sc.getSuperclass()) {
836
if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {
837
return true;
838
}
839
}
840
return false;
841
}
842
843
// Check the existence and signature of main and abort if incorrect
844
static void validateMainClass(Class<?> mainClass) {
845
Method mainMethod = null;
846
try {
847
mainMethod = mainClass.getMethod("main", String[].class);
848
} catch (NoSuchMethodException nsme) {
849
// invalid main or not FX application, abort with an error
850
abort(null, "java.launcher.cls.error4", mainClass.getName(),
851
JAVAFX_APPLICATION_CLASS_NAME);
852
} catch (Throwable e) {
853
if (mainClass.getModule().isNamed()) {
854
abort(e, "java.launcher.module.error5",
855
mainClass.getName(), mainClass.getModule().getName(),
856
e.getClass().getName(), e.getLocalizedMessage());
857
} else {
858
abort(e, "java.launcher.cls.error7", mainClass.getName(),
859
e.getClass().getName(), e.getLocalizedMessage());
860
}
861
}
862
863
/*
864
* getMethod (above) will choose the correct method, based
865
* on its name and parameter type, however, we still have to
866
* ensure that the method is static and returns a void.
867
*/
868
int mod = mainMethod.getModifiers();
869
if (!Modifier.isStatic(mod)) {
870
abort(null, "java.launcher.cls.error2", "static",
871
mainMethod.getDeclaringClass().getName());
872
}
873
if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
874
abort(null, "java.launcher.cls.error3",
875
mainMethod.getDeclaringClass().getName());
876
}
877
}
878
879
private static final String encprop = "sun.jnu.encoding";
880
private static String encoding = null;
881
private static boolean isCharsetSupported = false;
882
883
/*
884
* converts a c or a byte array to a platform specific string,
885
* previously implemented as a native method in the launcher.
886
*/
887
static String makePlatformString(boolean printToStderr, byte[] inArray) {
888
initOutput(printToStderr);
889
if (encoding == null) {
890
encoding = System.getProperty(encprop);
891
isCharsetSupported = Charset.isSupported(encoding);
892
}
893
try {
894
String out = isCharsetSupported
895
? new String(inArray, encoding)
896
: new String(inArray);
897
return out;
898
} catch (UnsupportedEncodingException uee) {
899
abort(uee, null);
900
}
901
return null; // keep the compiler happy
902
}
903
904
static String[] expandArgs(String[] argArray) {
905
List<StdArg> aList = new ArrayList<>();
906
for (String x : argArray) {
907
aList.add(new StdArg(x));
908
}
909
return expandArgs(aList);
910
}
911
912
static String[] expandArgs(List<StdArg> argList) {
913
ArrayList<String> out = new ArrayList<>();
914
if (trace) {
915
System.err.println("Incoming arguments:");
916
}
917
for (StdArg a : argList) {
918
if (trace) {
919
System.err.println(a);
920
}
921
if (a.needsExpansion) {
922
File x = new File(a.arg);
923
File parent = x.getParentFile();
924
String glob = x.getName();
925
if (parent == null) {
926
parent = new File(".");
927
}
928
try (DirectoryStream<Path> dstream =
929
Files.newDirectoryStream(parent.toPath(), glob)) {
930
int entries = 0;
931
for (Path p : dstream) {
932
out.add(p.normalize().toString());
933
entries++;
934
}
935
if (entries == 0) {
936
out.add(a.arg);
937
}
938
} catch (Exception e) {
939
out.add(a.arg);
940
if (trace) {
941
System.err.println("Warning: passing argument as-is " + a);
942
System.err.print(e);
943
}
944
}
945
} else {
946
out.add(a.arg);
947
}
948
}
949
String[] oarray = new String[out.size()];
950
out.toArray(oarray);
951
952
if (trace) {
953
System.err.println("Expanded arguments:");
954
for (String x : oarray) {
955
System.err.println(x);
956
}
957
}
958
return oarray;
959
}
960
961
/* duplicate of the native StdArg struct */
962
private static class StdArg {
963
final String arg;
964
final boolean needsExpansion;
965
StdArg(String arg, boolean expand) {
966
this.arg = arg;
967
this.needsExpansion = expand;
968
}
969
// protocol: first char indicates whether expansion is required
970
// 'T' = true ; needs expansion
971
// 'F' = false; needs no expansion
972
StdArg(String in) {
973
this.arg = in.substring(1);
974
needsExpansion = in.charAt(0) == 'T';
975
}
976
public String toString() {
977
return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';
978
}
979
}
980
981
static final class FXHelper {
982
983
private static final String JAVAFX_GRAPHICS_MODULE_NAME =
984
"javafx.graphics";
985
986
private static final String JAVAFX_LAUNCHER_CLASS_NAME =
987
"com.sun.javafx.application.LauncherImpl";
988
989
/*
990
* The launch method used to invoke the JavaFX launcher. These must
991
* match the strings used in the launchApplication method.
992
*
993
* Command line JavaFX-App-Class Launch mode FX Launch mode
994
* java -cp fxapp.jar FXClass N/A LM_CLASS "LM_CLASS"
995
* java -cp somedir FXClass N/A LM_CLASS "LM_CLASS"
996
* java -jar fxapp.jar Present LM_JAR "LM_JAR"
997
* java -jar fxapp.jar Not Present LM_JAR "LM_JAR"
998
* java -m module/class [1] N/A LM_MODULE "LM_MODULE"
999
* java -m module N/A LM_MODULE "LM_MODULE"
1000
*
1001
* [1] - JavaFX-Application-Class is ignored when modular args are used, even
1002
* if present in a modular jar
1003
*/
1004
private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";
1005
private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";
1006
private static final String JAVAFX_LAUNCH_MODE_MODULE = "LM_MODULE";
1007
1008
/*
1009
* FX application launcher and launch method, so we can launch
1010
* applications with no main method.
1011
*/
1012
private static String fxLaunchName = null;
1013
private static String fxLaunchMode = null;
1014
1015
private static Class<?> fxLauncherClass = null;
1016
private static Method fxLauncherMethod = null;
1017
1018
/*
1019
* Set the launch params according to what was passed to LauncherHelper
1020
* so we can use the same launch mode for FX. Abort if there is any
1021
* issue with loading the FX runtime or with the launcher method.
1022
*/
1023
private static void setFXLaunchParameters(String what, int mode) {
1024
1025
// find the module with the FX launcher
1026
Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
1027
if (!om.isPresent()) {
1028
abort(null, "java.launcher.cls.error5");
1029
}
1030
1031
// load the FX launcher class
1032
fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME);
1033
if (fxLauncherClass == null) {
1034
abort(null, "java.launcher.cls.error5");
1035
}
1036
1037
try {
1038
/*
1039
* signature must be:
1040
* public static void launchApplication(String launchName,
1041
* String launchMode, String[] args);
1042
*/
1043
fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",
1044
String.class, String.class, String[].class);
1045
1046
// verify launcher signature as we do when validating the main method
1047
int mod = fxLauncherMethod.getModifiers();
1048
if (!Modifier.isStatic(mod)) {
1049
abort(null, "java.launcher.javafx.error1");
1050
}
1051
if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {
1052
abort(null, "java.launcher.javafx.error1");
1053
}
1054
} catch (NoSuchMethodException ex) {
1055
abort(ex, "java.launcher.cls.error5", ex);
1056
}
1057
1058
fxLaunchName = what;
1059
switch (mode) {
1060
case LM_CLASS:
1061
fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;
1062
break;
1063
case LM_JAR:
1064
fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;
1065
break;
1066
case LM_MODULE:
1067
fxLaunchMode = JAVAFX_LAUNCH_MODE_MODULE;
1068
break;
1069
default:
1070
// should not have gotten this far...
1071
throw new InternalError(mode + ": Unknown launch mode");
1072
}
1073
}
1074
1075
public static void main(String... args) throws Exception {
1076
if (fxLauncherMethod == null
1077
|| fxLaunchMode == null
1078
|| fxLaunchName == null) {
1079
throw new RuntimeException("Invalid JavaFX launch parameters");
1080
}
1081
// launch appClass via fxLauncherMethod
1082
fxLauncherMethod.invoke(null,
1083
new Object[] {fxLaunchName, fxLaunchMode, args});
1084
}
1085
}
1086
1087
/**
1088
* Called by the launcher to list the observable modules.
1089
*/
1090
static void listModules() {
1091
initOutput(System.out);
1092
1093
ModuleBootstrap.limitedFinder().findAll().stream()
1094
.sorted(new JrtFirstComparator())
1095
.forEach(LauncherHelper::showModule);
1096
}
1097
1098
/**
1099
* Called by the launcher to show the resolved modules
1100
*/
1101
static void showResolvedModules() {
1102
initOutput(System.out);
1103
1104
ModuleLayer bootLayer = ModuleLayer.boot();
1105
Configuration cf = bootLayer.configuration();
1106
1107
cf.modules().stream()
1108
.map(ResolvedModule::reference)
1109
.sorted(new JrtFirstComparator())
1110
.forEach(LauncherHelper::showModule);
1111
}
1112
1113
/**
1114
* Called by the launcher to describe a module
1115
*/
1116
static void describeModule(String moduleName) {
1117
initOutput(System.out);
1118
1119
ModuleFinder finder = ModuleBootstrap.limitedFinder();
1120
ModuleReference mref = finder.find(moduleName).orElse(null);
1121
if (mref == null) {
1122
abort(null, "java.launcher.module.error4", moduleName);
1123
}
1124
ModuleDescriptor md = mref.descriptor();
1125
1126
// one-line summary
1127
showModule(mref);
1128
1129
// unqualified exports (sorted by package)
1130
md.exports().stream()
1131
.filter(e -> !e.isQualified())
1132
.sorted(Comparator.comparing(Exports::source))
1133
.map(e -> Stream.concat(Stream.of(e.source()),
1134
toStringStream(e.modifiers()))
1135
.collect(Collectors.joining(" ")))
1136
.forEach(sourceAndMods -> ostream.format("exports %s%n", sourceAndMods));
1137
1138
// dependences
1139
for (Requires r : md.requires()) {
1140
String nameAndMods = Stream.concat(Stream.of(r.name()),
1141
toStringStream(r.modifiers()))
1142
.collect(Collectors.joining(" "));
1143
ostream.format("requires %s", nameAndMods);
1144
finder.find(r.name())
1145
.map(ModuleReference::descriptor)
1146
.filter(ModuleDescriptor::isAutomatic)
1147
.ifPresent(any -> ostream.print(" automatic"));
1148
ostream.println();
1149
}
1150
1151
// service use and provides
1152
for (String s : md.uses()) {
1153
ostream.format("uses %s%n", s);
1154
}
1155
for (Provides ps : md.provides()) {
1156
String names = ps.providers().stream().collect(Collectors.joining(" "));
1157
ostream.format("provides %s with %s%n", ps.service(), names);
1158
1159
}
1160
1161
// qualified exports
1162
for (Exports e : md.exports()) {
1163
if (e.isQualified()) {
1164
String who = e.targets().stream().collect(Collectors.joining(" "));
1165
ostream.format("qualified exports %s to %s%n", e.source(), who);
1166
}
1167
}
1168
1169
// open packages
1170
for (Opens opens: md.opens()) {
1171
if (opens.isQualified())
1172
ostream.print("qualified ");
1173
String sourceAndMods = Stream.concat(Stream.of(opens.source()),
1174
toStringStream(opens.modifiers()))
1175
.collect(Collectors.joining(" "));
1176
ostream.format("opens %s", sourceAndMods);
1177
if (opens.isQualified()) {
1178
String who = opens.targets().stream().collect(Collectors.joining(" "));
1179
ostream.format(" to %s", who);
1180
}
1181
ostream.println();
1182
}
1183
1184
// non-exported/non-open packages
1185
Set<String> concealed = new TreeSet<>(md.packages());
1186
md.exports().stream().map(Exports::source).forEach(concealed::remove);
1187
md.opens().stream().map(Opens::source).forEach(concealed::remove);
1188
concealed.forEach(p -> ostream.format("contains %s%n", p));
1189
}
1190
1191
/**
1192
* Prints a single line with the module name, version and modifiers
1193
*/
1194
private static void showModule(ModuleReference mref) {
1195
ModuleDescriptor md = mref.descriptor();
1196
ostream.print(md.toNameAndVersion());
1197
mref.location()
1198
.filter(uri -> !isJrt(uri))
1199
.ifPresent(uri -> ostream.format(" %s", uri));
1200
if (md.isOpen())
1201
ostream.print(" open");
1202
if (md.isAutomatic())
1203
ostream.print(" automatic");
1204
ostream.println();
1205
}
1206
1207
/**
1208
* A ModuleReference comparator that considers modules in the run-time
1209
* image to be less than modules than not in the run-time image.
1210
*/
1211
private static class JrtFirstComparator implements Comparator<ModuleReference> {
1212
private final Comparator<ModuleReference> real;
1213
1214
JrtFirstComparator() {
1215
this.real = Comparator.comparing(ModuleReference::descriptor);
1216
}
1217
1218
@Override
1219
public int compare(ModuleReference a, ModuleReference b) {
1220
if (isJrt(a)) {
1221
return isJrt(b) ? real.compare(a, b) : -1;
1222
} else {
1223
return isJrt(b) ? 1 : real.compare(a, b);
1224
}
1225
}
1226
}
1227
1228
private static <T> Stream<String> toStringStream(Set<T> s) {
1229
return s.stream().map(e -> e.toString().toLowerCase());
1230
}
1231
1232
private static boolean isJrt(ModuleReference mref) {
1233
return isJrt(mref.location().orElse(null));
1234
}
1235
1236
private static boolean isJrt(URI uri) {
1237
return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));
1238
}
1239
1240
}
1241
1242