/
Capsule.java
5615 lines (5001 loc) · 228 KB
/
Capsule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Capsule
* Copyright (c) 2014-2015, Parallel Universe Software Co. and Contributors. All rights reserved.
*
* This program and the accompanying materials are licensed under the terms
* of the Eclipse Public License v1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.Permission;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.Properties;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.RandomAccess;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import static java.util.Collections.*;
import static java.util.Arrays.asList;
/**
* An application capsule.
* <p>
* This API is to be used by caplets (custom capsules) to programmatically (rather than declaratively) configure the capsule and possibly provide custom behavior.
*
* <h2>Writing Caplets</h2>
*
* All non-final protected methods may be overridden by caplets. These methods will usually be called once, but they must be idempotent,
* i.e. if called numerous times they must always return the same value, and produce the same effect as if called once.
* <br>
* Overridden methods need not be thread-safe, and are guaranteed to be called by a single thread at a time.
* <br>
* Overridable (non-final) methods <b>must never</b> be called directly by caplet code, except by their overrides.
* <p>
* Final methods implement various utility or accessors, which may be freely used by caplets.
* <p>
* Caplets might consider overriding some of the following powerful methods:
* {@link #attribute(Map.Entry) attribute}, {@link #getVarValue(String) getVarValue},
* {@link #prelaunch(List, List) prelaunch}.
* <p>
* For command line option handling, see {@link #OPTION(String, String, String, String) OPTION}.<br/>
* Attributes should be registered with {@link #ATTRIBUTE(String, Object, Object, boolean, String) ATTRIBUTE}.
*
* <h3>Handling Files</h3>
*
* Capsule uses a two-staged <i>file resolution mechanism</i>.
*
* When a manifest property contains a reference to anything that might resolve to a file (or multiple files) -- e.g. the name of a Maven artifact --
* the string containing the reference is passed to the {@link #lookup(java.lang.String, java.lang.String, java.util.Map.Entry, java.lang.Object) lookup} method,
* which returns an opaque handle.
*
* When the application launch command line is rendered (or any configuration file that is interpreted by an outside process), the file handles
* are passed to the {@link #resolve(java.lang.Object) resolve} method, which turns the handle into a list of {@code Path}s.
*
* The operation of {@code lookup} and {@code resolve} can be customized by a caplet, but that process is intentionally left undocumented
* until that process is finalized.
*
* @author pron
*/
public class Capsule implements Runnable, InvocationHandler {
public static final String VERSION = "1.0";
/*
* This class follows some STRICT RULES:
*
* 1. IT MUST COMPILE TO A SINGLE CLASS FILE (so it must not contain nested or inner classes).
* 2. IT MUST ONLY REFERENCE CLASSES IN THE JDK.
* 3. ALL METHODS MUST BE PURE OR, AT LEAST, IDEMPOTENT (with the exception of the launch method, and the constructor).
*
* Rules #1 and #2 ensure that fat capsules will work with only Capsule.class included in the JAR. Rule #2 helps enforcing rules #1 and #3.
* Rule #3 ensures methods can be called in any order (after construction completes), and makes maintenance and evolution of Capsule simpler.
* This class contains several strange hacks to comply with rule #1.
*
* Also, the code is not meant to be the most efficient, but methods should be as independent and stateless as possible.
* Other than those few methods called in the constructor, all others are can be called in any order, and don't rely on any state.
*
* We do a lot of data transformations that could benefit from Java 8's lambdas+streams, but we want Capsule to support Java 7.
*
* The JavaDoc could really benefit from https://bugs.openjdk.java.net/browse/JDK-4085608 to categorize methods into
* Caplet overrides properties, and utility categories.
*
*
* Caplet Hierarchy (or chain)
* ---------------------------
*
* Capsule subclasses, i.e. caplets, may be arranged in a dynamic "inheritance" hierarchy, where each caplet modifies, or "subclasses"
* the previous ones in the chain.
* The first caplet in the chain (the highest in the hierarchy) is referenced by the 'oc' field, the last is referenced by 'cc', and
* the previous caplet, the "superclass" is referenced by 'sup':
*
* ____ ____ ____ ____
* | | sup | | sup | | sup | |
* | OC | <----- | | <----- | | <----- | CC |
* |____| |____| |____| |____|
*
* A wrapping capsule is inserted into the chain following the wrapped capsule.
*/
//<editor-fold defaultstate="collapsed" desc="Constants">
/////////// Constants ///////////////////////////////////
private static final long START = System.nanoTime();
private static final Map<String, Object[]> OPTIONS = new LinkedHashMap<>(20);
private static final Map<String, Object[]> ATTRIBS = new LinkedHashMap<>(60);
private static Properties PROPERTIES = new Properties(System.getProperties());
// standard values
private static final String PROP_JAVA_VERSION = "java.version";
private static final String PROP_JAVA_HOME = "java.home";
private static final String PROP_OS_NAME = "os.name";
private static final String PROP_USER_HOME = "user.home";
private static final String PROP_JAVA_LIBRARY_PATH = "java.library.path";
private static final String PROP_FILE_SEPARATOR = "file.separator";
private static final String PROP_PATH_SEPARATOR = "path.separator";
private static final String PROP_JAVA_SECURITY_POLICY = "java.security.policy";
private static final String PROP_JAVA_SECURITY_MANAGER = "java.security.manager";
private static final String PROP_TMP_DIR = "java.io.tmpdir";
private static final String ATTR_MANIFEST_VERSION = "Manifest-Version";
private static final String ATTR_PREMAIN_CLASS = "Premain-Class";
private static final String ATTR_MAIN_CLASS = "Main-Class";
private static final String ATTR_CLASS_PATH = "Class-Path";
private static final String ATTR_IMPLEMENTATION_VERSION = "Implementation-Version";
private static final String ATTR_IMPLEMENTATION_TITLE = "Implementation-Title";
private static final String ATTR_IMPLEMENTATION_VENDOR = "Implementation-Vendor";
private static final String ATTR_IMPLEMENTATION_URL = "Implementation-URL";
private static final String FILE_SEPARATOR = System.getProperty(PROP_FILE_SEPARATOR);
private static final char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
private static final String PATH_SEPARATOR = System.getProperty(PROP_PATH_SEPARATOR);
private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
// Operating systems
private static final String OS_WINDOWS = "windows";
private static final String OS_MACOS = "macos";
private static final String OS_LINUX = "linux";
private static final String OS_SOLARIS = "solaris";
private static final String OS_BSD = "bsd";
private static final String OS_AIX = "aix";
private static final String OS_HP_UX = "hp-ux";
private static final String OS_UNIX = "unix";
private static final String OS_POSIX = "posix";
private static final String OS_VMS = "vms";
private static final String OS = getProperty(PROP_OS_NAME).toLowerCase();
private static final Set<String> PLATFORMS = immutableSet(OS_WINDOWS, OS_MACOS, OS_LINUX, OS_SOLARIS, OS_BSD, OS_AIX, OS_POSIX, OS_UNIX, OS_POSIX, OS_VMS);
private static final String PLATFORM = getOS();
private static final String ENV_CACHE_DIR = "CAPSULE_CACHE_DIR";
private static final String ENV_CACHE_NAME = "CAPSULE_CACHE_NAME";
// misc
private static final String CAPSULE_PROP_PREFIX = "capsule.";
private static final String CACHE_DEFAULT_NAME = "capsule";
private static final String APP_CACHE_NAME = "apps";
private static final String LOCK_FILE_NAME = ".lock";
private static final String TIMESTAMP_FILE_NAME = ".extracted";
private static final String CACHE_NONE = "NONE";
private static final String SEPARATOR_DOT = "\\.";
private static final Path WINDOWS_PROGRAM_FILES_1 = Paths.get("C:", "Program Files");
private static final Path WINDOWS_PROGRAM_FILES_2 = Paths.get("C:", "Program Files (x86)");
private static final int WINDOWS_MAX_CMD = 32500; // actually 32768 - http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx
private static final ClassLoader MY_CLASSLOADER = Capsule.class.getClassLoader();
private static final Permission PERM_UNSAFE_OVERRIDE = new RuntimePermission("unsafeOverride");
private static final int SOCKET_TIMEOUT = 30000; // Keep high enough for container-based capsules
// Lifecycle
private static final int STAGE_NONE = 0;
private static final int STAGE_LAUNCH = 1;
private static final int STAGE_LIFTOFF = 2;
// options
private static final int OPTION_DEFAULT = 0;
private static final int OPTION_METHOD = 1;
private static final int OPTION_WRAPPER_ONLY = 2;
private static final int OPTION_DESC = 3;
// attributes
private static final int ATTRIB_TYPE = 0;
private static final int ATTRIB_DEFAULT = 1;
private static final int ATTRIB_MODAL = 2;
private static final int ATTRIB_DESC = 3;
// messages
private static final int MESSAGE_EXIT = 1;
private static final int MESSAGE_START_JMX = 2;
private static final int MESSAGE_JMX_URL = 3;
// properties
private static final String PROP_VERSION = OPTION("capsule.version", "false", "printVersion", "Prints the capsule and application versions.");
private static final String PROP_MODES = OPTION("capsule.modes", "false", "printModes", "Prints all available capsule modes.");
private static final String PROP_PRINT_JRES = OPTION("capsule.jvms", "false", "printJVMs", "Prints a list of all JVM installations found.");
private static final String PROP_MERGE = OPTION("capsule.merge", null, "mergeCapsules", true, "Merges a wrapper capsule with a wrapped capsule.");
private static final String PROP_HELP = OPTION("capsule.help", "false", "printHelp", "Prints this help message.");
private static final String PROP_MODE = OPTION("capsule.mode", null, null, "Picks the capsule mode to run.");
private static final String PROP_RESET = OPTION("capsule.reset", "false", null, "Resets the capsule cache before launching. The capsule to be re-extracted (if applicable), and other possibly cached files will be recreated.");
private static final String PROP_LOG_LEVEL = OPTION("capsule.log", "quiet", null, "Picks a log level. Must be one of none, quiet, verbose, or debug.");
private static final String PROP_CAPSULE_JAVA_HOME = OPTION("capsule.java.home", null, null, "Sets the location of the Java home (JVM installation directory) to use; If \'current\' forces the use of the JVM that launched the capsule.");
private static final String PROP_CAPSULE_JAVA_CMD = OPTION("capsule.java.cmd", null, null, "Sets the path to the Java executable to use.");
private static final String PROP_JVM_ARGS = OPTION("capsule.jvm.args", null, null, "Sets additional JVM arguments to use when running the application.");
private static final String PROP_PORT = "capsule.port";
private static final String PROP_ADDRESS = "capsule.address";
private static final String PROP_TRAMPOLINE = "capsule.trampoline";
private static final String PROP_PROFILE = "capsule.profile";
/*
* Map.Entry<String, T> was chosen to represent an attribute because of rules 1 and 2.
*/
/** The application's name. E.g. {@code "The Best Word Processor"} */
protected static final Entry<String, String> ATTR_APP_NAME = ATTRIBUTE("Application-Name", T_STRING(), null, false, "The application's name");
/** The application's unique ID. E.g. {@code "com.acme.bestwordprocessor"} */
protected static final Entry<String, String> ATTR_APP_ID = ATTRIBUTE("Application-Id", T_STRING(), null, false, "The application's name");
protected static final Entry<String, String> ATTR_APP_VERSION = ATTRIBUTE("Application-Version", T_STRING(), null, false, "The application's version string");
protected static final Entry<String, List<String>> ATTR_CAPLETS = ATTRIBUTE("Caplets", T_LIST(T_STRING()), null, false, "A list of names of caplet classes -- if embedded in the capsule -- or Maven coordinates of caplet artifacts that will be applied to the capsule in the order they are listed");
private static final Entry<String, String> ATTR_LOG_LEVEL = ATTRIBUTE("Capsule-Log-Level", T_STRING(), null, false, "The capsule's default log level");
private static final Entry<String, String> ATTR_MODE_DESC = ATTRIBUTE("Description", T_STRING(), null, true, "Contains the description of its respective mode");
protected static final Entry<String, String> ATTR_APP_CLASS = ATTRIBUTE("Application-Class", T_STRING(), null, true, "The main application class");
protected static final Entry<String, String> ATTR_APP_ARTIFACT = ATTRIBUTE("Application", T_STRING(), null, true, "The Maven coordinates of the application's main JAR or the path of the main JAR within the capsule");
private static final Entry<String, Object> ATTR_SCRIPT = ATTRIBUTE("Application-Script", T_FILE(), null, true, "A startup script to be run *instead* of `Application-Class`, given as a path relative to the capsule's root");
protected static final Entry<String, String> ATTR_MIN_JAVA_VERSION = ATTRIBUTE("Min-Java-Version", T_STRING(), null, true, "The lowest Java version required to run the application");
protected static final Entry<String, String> ATTR_JAVA_VERSION = ATTRIBUTE("Java-Version", T_STRING(), null, true, "The highest version of the Java installation required to run the application");
protected static final Entry<String, Map<String, String>> ATTR_MIN_UPDATE_VERSION = ATTRIBUTE("Min-Update-Version", T_MAP(T_STRING(), T_STRING(), null), null, true, "A space-separated key-value ('=' separated) list mapping Java versions to the minimum update version required");
protected static final Entry<String, Boolean> ATTR_JDK_REQUIRED = ATTRIBUTE("JDK-Required", T_BOOL(), false, true, "Whether or not a JDK is required to launch the application");
protected static final Entry<String, Boolean> ATTR_AGENT = ATTRIBUTE("Capsule-Agent", T_BOOL(), false, true, "Whether this capsule should inject itself as an agent into the application.");
private static final Entry<String, List<String>> ATTR_ARGS = ATTRIBUTE("Args", T_LIST(T_STRING()), null, true,
"A list of command line arguments to be passed to the application; the UNIX shell-style special variables (`$*`, `$1`, `$2`, ...) can refer to the actual arguments passed on the capsule's command line; if no special var is used, the listed values will be prepended to the supplied arguments (i.e., as if `$*` had been listed last).");
private static final Entry<String, Map<String, String>> ATTR_ENV = ATTRIBUTE("Environment-Variables", T_MAP(T_STRING(), T_STRING(), null), null, true, "A list of environment variables that will be put in the applications environment; formatted \"var=value\" or \"var\"");
protected static final Entry<String, List<String>> ATTR_JVM_ARGS = ATTRIBUTE("JVM-Args", T_LIST(T_STRING()), null, true, "A list of JVM arguments that will be used to launch the application's Java process");
protected static final Entry<String, Map<String, String>> ATTR_SYSTEM_PROPERTIES = ATTRIBUTE("System-Properties", T_MAP(T_STRING(), T_STRING(), ""), null, true, "A list of system properties that will be defined in the applications JVM; formatted \"prop=value\" or \"prop\"");
protected static final Entry<String, List<Object>> ATTR_APP_CLASS_PATH = ATTRIBUTE("App-Class-Path", T_LIST(T_FILE()), null, true, "A list of JARs, relative to the capsule root, that will be put on the application's classpath, in the order they are listed");
protected static final Entry<String, Boolean> ATTR_CAPSULE_IN_CLASS_PATH = ATTRIBUTE("Capsule-In-Class-Path", T_BOOL(), true, true, "Whether or not the capsule JAR itself is on the application's classpath");
protected static final Entry<String, List<Object>> ATTR_BOOT_CLASS_PATH = ATTRIBUTE("Boot-Class-Path", T_LIST(T_FILE()), null, true, "A list of JARs, dependencies, and/or directories, relative to the capsule root, that will be used as the application's boot classpath");
protected static final Entry<String, List<Object>> ATTR_BOOT_CLASS_PATH_A = ATTRIBUTE("Boot-Class-Path-A", T_LIST(T_FILE()), null, true, "A list of JARs dependencies, and/or directories, relative to the capsule root, that will be appended to the applications default boot classpath");
protected static final Entry<String, List<Object>> ATTR_BOOT_CLASS_PATH_P = ATTRIBUTE("Boot-Class-Path-P", T_LIST(T_FILE()), null, true, "A list of JARs dependencies, and/or directories, relative to the capsule root, that will be prepended to the applications default boot classpath");
protected static final Entry<String, List<Object>> ATTR_LIBRARY_PATH_A = ATTRIBUTE("Library-Path-A", T_LIST(T_FILE()), null, true, "A list of JARs and/or directories, relative to the capsule root, to be appended to the default native library path");
protected static final Entry<String, List<Object>> ATTR_LIBRARY_PATH_P = ATTRIBUTE("Library-Path-P", T_LIST(T_FILE()), null, true, "a list of JARs and/or directories, relative to the capsule root, to be prepended to the default native library path");
protected static final Entry<String, String> ATTR_SECURITY_MANAGER = ATTRIBUTE("Security-Manager", T_STRING(), null, true, "The name of a class that will serve as the application's security-manager");
protected static final Entry<String, String> ATTR_SECURITY_POLICY = ATTRIBUTE("Security-Policy", T_STRING(), null, true, "A security policy file, relative to the capsule root, that will be used as the security policy");
protected static final Entry<String, String> ATTR_SECURITY_POLICY_A = ATTRIBUTE("Security-Policy-A", T_STRING(), null, true, "A security policy file, relative to the capsule root, that will be appended to the default security policy");
protected static final Entry<String, Map<Object, String>> ATTR_JAVA_AGENTS = ATTRIBUTE("Java-Agents", T_MAP(T_FILE(), T_STRING(), ""), null, true, "A list of Java agents used by the application; formatted \"agent\" or \"agent=arg1,arg2...\", where agent is either the path to a JAR relative to the capsule root, or a Maven coordinate of a dependency");
protected static final Entry<String, Map<Object, String>> ATTR_NATIVE_AGENTS = ATTRIBUTE("Native-Agents", T_MAP(T_FILE(getNativeLibExtension()), T_STRING(), ""), null, true,
"A list of native JVMTI agents used by the application; formatted \"agent\" or \"agent=arg1,arg2...\", where agent is either the path to a native library, without the platform-specific suffix, relative to the capsule root. The native library file(s) can be embedded in the capsule or listed as Maven native dependencies using the Native-Dependencies-... attributes.");
protected static final Entry<String, List<Object>> ATTR_DEPENDENCIES = ATTRIBUTE("Dependencies", T_LIST(T_FILE()), null, true, "A list of Maven dependencies given as groupId:artifactId:version[(excludeGroupId:excludeArtifactId,...)]");
protected static final Entry<String, Map<Object, String>> ATTR_NATIVE_DEPENDENCIES = ATTRIBUTE("Native-Dependencies", T_MAP(T_FILE(getNativeLibExtension()), T_STRING(), ""), null, true, "A list of Maven dependencies consisting of native library artifacts; each item can be a comma separated pair, with the second component being a new name to give the download artifact");
// outgoing
private static final String VAR_CAPSULE_APP = "CAPSULE_APP";
private static final String VAR_CAPSULE_DIR = "CAPSULE_DIR";
private static final String VAR_CAPSULE_JAR = "CAPSULE_JAR";
private static final String VAR_CLASSPATH = "CLASSPATH";
private static final String VAR_JAVA_HOME = "JAVA_HOME";
private static final String PROP_CAPSULE_JAR = "capsule.jar";
private static final String PROP_CAPSULE_DIR = "capsule.dir";
private static final String PROP_CAPSULE_APP = "capsule.app";
private static final String PROP_CAPSULE_APP_PID = "capsule.app.pid";
// logging
private static final String LOG_PREFIX = "CAPSULE: ";
private static final String LOG_AGENT_PREFIX = "CAPSULE AGENT: ";
protected static final int LOG_NONE = 0;
protected static final int LOG_QUIET = 1;
protected static final int LOG_VERBOSE = 2;
protected static final int LOG_DEBUG = 3;
private static final int PROFILE = emptyOrTrue(System.getProperty(PROP_PROFILE)) ? LOG_QUIET : LOG_DEBUG;
//</editor-fold>
//<editor-fold desc="Main">
/////////// Main ///////////////////////////////////
protected static final PrintStream STDOUT = System.out;
protected static final PrintStream STDERR = System.err;
private static volatile Integer LOG_LEVEL;
private static Path CACHE_DIR;
private static Capsule CAPSULE;
private static boolean AGENT;
final static Capsule myCapsule(Class<? extends Capsule> capsuleClass, List<String> args) {
if (CAPSULE == null) {
final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(MY_CLASSLOADER);
Capsule capsule = newCapsule(MY_CLASSLOADER, findOwnJarFile(capsuleClass));
clearContext();
if ((AGENT || capsule.isEmptyCapsule()) && args != null && !args.isEmpty()) {
processCmdLineOptions(args, ManagementFactory.getRuntimeMXBean().getInputArguments());
if (capsule.isEmptyCapsule())
capsule = capsule.setTarget(args.remove(0));
} else {
processOptions();
}
CAPSULE = capsule.oc; // TODO: capsule or oc ???
} finally {
Thread.currentThread().setContextClassLoader(ccl);
}
}
return CAPSULE;
}
final static Capsule myCapsule(List<String> args) {
return myCapsule(null, args);
}
public static final void main(String[] args) {
System.exit(main0(args));
}
@SuppressWarnings({"BroadCatchBlock", "UnusedAssignment"})
private static int main0(String[] args0) {
List<String> args = new ArrayList<>(asList(args0)); // list must be mutable b/c myCapsule() might mutate it
Capsule capsule = null;
try {
capsule = myCapsule(args);
args = unmodifiableList(args);
if (isWrapperFactoryCapsule(capsule)) {
capsule = null; // help gc
return runOtherCapsule(args);
}
if (runActions(capsule, args))
return 0;
return capsule.launch(args);
} catch (Throwable t) {
if (capsule != null) {
capsule.cleanup1();
capsule.onError(t);
} else
printError(t, capsule);
return 1;
}
}
public static void premain(String agentArgs, Instrumentation inst) {
AGENT = true;
PROPERTIES = new Properties(System.getProperties());
Capsule capsule = null;
try {
processOptions();
capsule = myCapsule(Capsule.class, agentArgs != null ? new ArrayList<>(split(agentArgs, "\\s+")) : null);
for (Capsule c = capsule.cc; c != null; c = c.sup)
c.agent(inst);
} catch (Throwable t) {
if (capsule != null) {
capsule.cleanup1();
capsule.onError(t);
} else
printError(LOG_QUIET, t);
}
}
/**
* @deprecated marked deprecated to exclude from javadoc
*/
public static Object getCapsule(String capletClassName) {
return CAPSULE.cc.sup(capletClassName);
}
//<editor-fold defaultstate="collapsed" desc="Error Reporting">
/////////// Error Reporting ///////////////////////////////////
private static void printError(int level, Throwable t) {
if (!isLogging(level))
return;
STDERR.print((AGENT ? "CAPSULE AGENT" : "CAPSULE") + " EXCEPTION: " + t.getMessage());
if (hasContext() && (t.getMessage() == null || t.getMessage().length() < 50))
STDERR.print(" while processing " + getContext());
if (getLogLevel(getProperty0(PROP_LOG_LEVEL)) >= LOG_VERBOSE) {
STDERR.println();
deshadow(t).printStackTrace(STDERR);
} else
STDERR.println(" (for stack trace, run with -D" + PROP_LOG_LEVEL + "=verbose)");
}
private static void printError(Throwable t, Capsule capsule) {
printError(LOG_QUIET, t);
if (!AGENT && t instanceof IllegalArgumentException)
printUsage(capsule);
}
private static void printUsage(Capsule capsule) {
printHelp(capsule != null ? capsule.isWrapperCapsule() : true);
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Run Other Capsule">
/////////// Run Other Capsule ///////////////////////////////////
private static boolean isWrapperFactoryCapsule(Capsule capsule) {
return capsule.isFactoryCapsule() && capsule.isWrapperCapsule() && capsule.getJarFile() != null;
}
private static int runOtherCapsule(List<String> args) {
final Path jar = CAPSULE.getJarFile();
CAPSULE = null; // help gc
return runMain(jar, args);
}
private static int runMain(Path jar, List<String> args) {
final String mainClass;
try {
mainClass = getMainClass(jar);
if (mainClass == null)
throw new IllegalArgumentException("JAR file " + jar + " is not an executable (does not have a main class)");
} catch (RuntimeException e) {
throw new IllegalArgumentException(jar + " does not exist or does appear to be a valid JAR", e);
}
try {
final Method main = newClassLoader0(null, jar).loadClass(mainClass).getMethod("main", String[].class);
try {
main.invoke(null, (Object) args.toArray(new String[0]));
return 0;
} catch (Exception e) {
deshadow(e).printStackTrace(STDERR);
return 1;
}
} catch (ReflectiveOperationException e) {
throw rethrow(e);
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Command Line">
/////////// Command Line ///////////////////////////////////
/**
* Registers a capsule command-line option. Must be called during the caplet's static initialization.
* <p>
* Capsule options are system properties beginning with the prefix "capsule.", normally passed to the capsule as -D flags on the command line.
* <p>
* Options can be top-level *actions* (like print dependency tree or list JVMs), in which case the {@code methodName} argument must
* be the name of a method used to launch the action instead of launching the capsule.
* <p>
* Options can have a default value, which will be automatically assigned to the system property if undefined. The default values
* {@code "true"} and {@code "false"} are treated specially. If one of them is the assigned default value, and the system property
* is defined with with a value of the empty string, then it will be re-assigned the value {@code "true"}.
* <p>
* <b>Simple Command Line Options for Wrapper Capsules</b><br>
* When the capsule serves as a wrapper (i.e. it's an empty capsule used to launch an executable artifact or another capsule)
* then the options can also be passed to the capsule as simple command line options (arguments starting with a hyphen),
* with the "capsule." prefix removed, and every '.' character replaced with a '-'.
* <p>
* These command line arguments will automatically be converted to system properties, which will take their value from the argument
* following the option (i.e. {@code -option value}), <i>unless</i> the option is given one of the special default values
* {@code "true"} or {@code "false"}, in which case it is treated as a flag with no arguments (note that an option with the default
* value {@code "true"} will therefore not be able to be turned off if simple options are used).
*
* @param defaultValue the option's default value ({@code "true"} and {@code "false"} are specially treated; see above).
* @param optionName the name of the system property for the option; must begin with {@code "capsule."}.
* @param methodName if non-null, then the option is a top-level action (like print dependency tree or list JVMs),
* and this is the method which will run the action.
* The method must accept a single {@code args} parameter of type {@code List<String>}.
* @param wrapperOnly whether or not the option is available in wrapper capsules only
* @param description a description of the option.
* @return the option's name
*/
protected static final String OPTION(String optionName, String defaultValue, String methodName, boolean wrapperOnly, String description) {
if (!optionName.startsWith(CAPSULE_PROP_PREFIX))
throw new IllegalArgumentException("Option name must start with " + CAPSULE_PROP_PREFIX + " but was " + optionName);
final Object[] conf = new Object[]{defaultValue, methodName, wrapperOnly, description};
final Object[] old = OPTIONS.get(optionName);
if (old != null) {
if (asList(conf).subList(0, conf.length - 1).equals(asList(old).subList(0, conf.length - 1))) // don't compare description
throw new IllegalStateException("Option " + optionName + " has a conflicting registration: " + Arrays.toString(old));
}
OPTIONS.put(optionName, conf);
return optionName;
}
/**
* Same as {@link #OPTION(String, String, String, boolean, String) OPTION(optionName, defaultValue, methodName, wrapperOnly, description)}.
*/
protected static final String OPTION(String optionName, String defaultValue, String methodName, String description) {
return OPTION(optionName, defaultValue, methodName, false, description);
}
private static boolean optionTakesArguments(String propertyName) {
final String defaultValue = (String) OPTIONS.get(propertyName)[OPTION_DEFAULT];
return !("false".equals(defaultValue) || "true".equals(defaultValue));
}
private static void processOptions() {
for (Map.Entry<String, Object[]> entry : OPTIONS.entrySet()) {
final String option = entry.getKey();
final String defval = (String) entry.getValue()[OPTION_DEFAULT];
if (getProperty0(option) == null && defval != null && !defval.equals("false")) // the last condition is for backwards compatibility
setProperty(option, defval);
else if (!optionTakesArguments(option) && "".equals(getProperty0(option)))
setProperty(option, "true");
}
}
private static void processCmdLineOptions(List<String> args, List<String> jvmArgs) {
while (!args.isEmpty()) {
if (!first(args).startsWith("-"))
break;
final String arg = args.remove(0);
String optarg = null;
if (arg.contains("="))
optarg = getAfter(arg, '=');
final String option = simpleToOption(getBefore(arg, '='));
if (option == null)
throw new IllegalArgumentException("Unrecognized option: " + arg);
// -D wins over simple flags
boolean overridden = false;
for (String x : jvmArgs) {
if (x.equals("-D" + option) || x.startsWith("-D" + option + "=")) {
overridden = true;
break;
}
}
if (optarg == null)
optarg = optionTakesArguments(option) ? args.remove(0) : "";
if (!overridden)
setProperty(option, optarg);
}
processOptions();
}
// visible for testing
@SuppressWarnings("unchecked")
static final boolean runActions(Capsule capsule, List<String> args) {
verifyAgent(false);
try {
boolean found = false;
for (Map.Entry<String, Object[]> entry : OPTIONS.entrySet()) {
if (entry.getValue()[OPTION_METHOD] != null && systemPropertyEmptyOrNotFalse(entry.getKey())) {
if (!capsule.isWrapperCapsule() && (Boolean) entry.getValue()[OPTION_WRAPPER_ONLY])
throw new IllegalStateException("Action " + entry.getKey() + " is availbale for wrapper capsules only.");
final Method m = getMethod(capsule, (String) entry.getValue()[OPTION_METHOD], List.class);
m.invoke(capsule.cc.sup((Class<? extends Capsule>) m.getDeclaringClass()), args);
found = true;
}
}
if (found)
capsule.cleanup1();
return found;
} catch (InvocationTargetException e) {
throw rethrow(e);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
private static String optionToSimple(String option) {
return "-" + camelCaseToDashed(option.substring(CAPSULE_PROP_PREFIX.length())).replace('.', '-');
}
private static String simpleToOption(String simple) {
if ("-h".equals(simple))
return PROP_HELP;
for (String option : OPTIONS.keySet()) {
if (simple.equals(optionToSimple(option)))
return option;
}
return null;
}
private static String camelCaseToDashed(String camel) {
return camel.replaceAll("([A-Z][a-z]+)", "-$1").toLowerCase();
}
private static boolean isCapsuleOption(String propertyName) {
return propertyName.startsWith(CAPSULE_PROP_PREFIX); // OPTIONS.containsKey(propertyName);
}
//</editor-fold>
//</editor-fold>
private static Map<String, List<Path>> JAVA_HOMES; // an optimization trick (can be injected by CapsuleLauncher)
private final Map<String, String> threads = new HashMap<>();
// fields marked /*final*/ are effectively final after finalizeCapsule
private /*final*/ Capsule oc; // first in chain
private /*final*/ Capsule cc; // last in chain
private /*final*/ Capsule sup; // previous in chain
private /*final*/ Capsule _ct; // a temp var
private final boolean wrapper;
private final Manifest manifest; // never null
private /*final*/ Path jarFile; // never null
private /*final*/ String appId; // null iff wrapper capsule wrapping a non-capsule JAR
private /*final*/ String mode;
private /*final*/ String nonCapsuleTarget;
private Path javaHome;
private String javaVersion;
private Path cacheDir;
private Path appDir;
private Path writableAppCache;
private boolean plainCache;
private boolean cacheUpToDate;
private FileLock appCacheLock;
private int lifecycleStage;
private String appArtifactMainClass;
private List<String> jvmArgs_;
private List<String> args_;
private List<Path> tmpFiles = new ArrayList<>();
private Process child;
private boolean agentCalled;
private MBeanServer origMBeanServer;
private MBeanServerConnection jmxConnection;
// Error reporting
private static final ThreadLocal<String> contextType_ = new ThreadLocal<>();
private static final ThreadLocal<String> contextKey_ = new ThreadLocal<>();
private static final ThreadLocal<String> contextValue_ = new ThreadLocal<>();
//
private Object socket;
private InetAddress address;
private int port;
private ObjectInput socketInput;
private ObjectOutput socketOutput;
//<editor-fold defaultstate="collapsed" desc="Constructors">
/////////// Constructors ///////////////////////////////////
/*
* The constructors and methods in this section may be reflectively called by CapsuleLauncher
*/
/**
* Constructs a capsule.
* <p>
* This constructor is used by a caplet that will be listed in the manifest's {@code Main-Class} attribute.
* <b>Caplets are encouraged to "override" the {@link #Capsule(Capsule) other constructor} so that they may be listed
* in the {@code Caplets} attribute.</b>
* <p>
* This constructor or that of a subclass must not make use of any registered capsule options,
* as they may not have been properly pre-processed yet.
*
* @param jarFile the path to the JAR file
*/
@SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"})
protected Capsule(Path jarFile) {
clearContext();
Objects.requireNonNull(jarFile, "jarFile can't be null");
this.oc = this;
this.cc = this;
this.sup = null;
this.jarFile = toAbsolutePath(jarFile);
final long start = System.nanoTime(); // can't use clock before log level is set
try (JarInputStream jis = openJarInputStream(jarFile)) {
this.manifest = jis.getManifest();
if (manifest == null)
throw new RuntimeException("Capsule " + jarFile + " does not have a manifest");
} catch (IOException e) {
throw new RuntimeException("Could not read JAR file " + jarFile, e);
}
setLogLevel(chooseLogLevel()); // temporary
log(LOG_VERBOSE, "Jar: " + jarFile);
log(LOG_VERBOSE, "Platform: " + PLATFORM);
this.wrapper = isEmptyCapsule(); // must be done before loadCaplets, to init their wrapper field, but this implies the application must be specified in the manifest
loadCaplets();
setLogLevel(chooseLogLevel()); // temporary
time("Load class " + this.getClass().getName(), START, start);
time("Read JAR in constructor", start);
if (!wrapper)
finalizeCapsule();
else if (isFactoryCapsule())
this.jarFile = null; // an empty factory capsule is marked this way.
clearContext();
}
/**
* Caplets that will be listed on the manifest's {@code Caplets} attribute must use this constructor.
* Caplets are required to have a constructor with the same signature as this constructor, and pass their arguments to up to this constructor.
*
* @param pred The capsule preceding this one in the chain (caplets must not access the passed capsule in their constructor).
*/
@SuppressWarnings("LeakingThisInConstructor")
protected Capsule(Capsule pred) {
this.oc = pred.oc;
this.cc = this;
time("Load class " + this.getClass().getName(), START);
clearContext();
// insertAfter(pred);
// copy final dields
this.wrapper = pred.wrapper;
this.manifest = pred.manifest;
this.jarFile = pred.jarFile;
}
final Capsule setTarget(String target) {
verifyCanCallSetTarget();
final Path jar = isDependency(target) ? firstOrNull(resolve(lookup(target, ATTR_APP_ARTIFACT))) : toAbsolutePath(path(target));
if (jar == null)
throw new RuntimeException(target + " not found.");
if (jar.equals(getJarFile())) // catch simple loops
throw new RuntimeException("Capsule wrapping loop detected with capsule " + getJarFile());
if (isFactoryCapsule()) {
this.jarFile = jar;
return this;
}
final Manifest man;
boolean isCapsule = false;
final long start = clock();
try (JarInputStream jis = openJarInputStream(jar)) {
man = jis.getManifest();
if (man == null || man.getMainAttributes().getValue(ATTR_MAIN_CLASS) == null)
throw new IllegalArgumentException(jar + " is not a capsule or an executable JAR");
for (JarEntry entry; (entry = jis.getNextJarEntry()) != null;) {
if (entry.getName().equals(Capsule.class.getName() + ".class")) {
isCapsule = true;
break;
}
}
} catch (IOException e) {
throw new RuntimeException("Could not read JAR file " + jar, e);
}
time("Read JAR in setTarget", start);
if (!isCapsule)
oc.nonCapsuleTarget = target;
else {
log(LOG_VERBOSE, "Wrapping capsule " + jar);
insertAfter(loadTargetCapsule(cc.getClass().getClassLoader(), jar).cc);
}
finalizeCapsule();
return this;
}
/**
* Called once the capsule construction has been completed (after loading of wrapped capsule, if applicable).
*/
protected void finalizeCapsule() {
if ((_ct = getCallTarget(Capsule.class)) != null)
_ct.finalizeCapsule();
else
finalizeCapsule0();
clearContext();
}
private void finalizeCapsule0() {
validateManifest(oc.manifest);
setLogLevel(chooseLogLevel());
oc.mode = chooseMode1();
initAppId();
if (getAppId() == null && !(hasAttribute(ATTR_APP_ARTIFACT) && !isDependency(getAttribute(ATTR_APP_ARTIFACT))))
throw new IllegalArgumentException("Could not determine app ID. Capsule jar " + getJarFile() + " should have the " + ATTR_APP_NAME + " manifest attribute.");
}
private void verifyCanCallSetTarget() {
if (getAppId() != null)
throw new IllegalStateException("Capsule is finalized");
if (!isEmptyCapsule())
throw new IllegalStateException("Capsule " + getJarFile() + " isn't empty");
}
private void loadCaplets() {
for (String caplet : getAttribute(ATTR_CAPLETS))
loadCaplet(caplet, cc).insertAfter(cc);
}
private void initAppId() {
if (oc.appId != null)
return;
log(LOG_VERBOSE, "Initializing app ID");
final String name = getAppIdNoVer();
if (name == null)
return;
final String version = getAttribute(ATTR_APP_VERSION);
oc.appId = name + (version != null ? "_" + version : "");
log(LOG_VERBOSE, "Initialized app ID: " + oc.appId);
}
protected final boolean isEmptyCapsule() {
return !hasAttribute(ATTR_APP_ARTIFACT) && !hasAttribute(ATTR_APP_CLASS) && !hasAttribute(ATTR_SCRIPT);
}
private void setStage(int stage) {
oc.lifecycleStage = stage;
}
private void verifyAtStage(int stage) {
if (oc.lifecycleStage != stage)
throw new IllegalStateException("This operation is not available at this stage in the capsule's lifecycle.");
}
protected final void verifyAfterStage(int stage) {
if (oc.lifecycleStage <= stage)
throw new IllegalStateException("This operation is not available at this stage in the capsule's lifecycle.");
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Caplet Chain">
/////////// Caplet Chain ///////////////////////////////////
private Capsule loadCaplet(String caplet, Capsule pred) {
log(LOG_VERBOSE, "Loading caplet: " + caplet);
if (isDependency(caplet) || caplet.endsWith(".jar")) {
final List<Path> jars = resolve(lookup(caplet, ATTR_CAPLETS));
if (jars.size() != 1)
throw new RuntimeException("The caplet " + caplet + " has transitive dependencies.");
return newCapsule(first(jars), pred);
} else
return newCapsule(caplet, pred);
}
private void insertAfter(Capsule pred) {
// private b/c this might be a security risk (wrapped capsule inserting a caplet after wrapper)
// and also because it might be too powerful and prevent us from adopting a different caplet chain implementation
log(LOG_VERBOSE, "Applying caplet " + this.getClass().getName());
if (sup == pred)
return;
if (pred != null) {
if (sup != null)
throw new IllegalStateException("Caplet " + this + " is already in the chain (after " + sup + ")");
if (!wrapper && pred.hasCaplet(this.getClass().getName())) {
log(LOG_VERBOSE, "Caplet " + this.getClass().getName() + " has already been applied.");
return;
}
this.sup = pred;
this.oc = sup.oc;
for (Capsule c = cc; c != this; c = c.sup)
c.oc = oc;
if (sup.cc == sup) { // I'm last
for (Capsule c = sup; c != null; c = c.sup)
c.cc = cc;
} else { // I'm in the middle
throw new IllegalArgumentException("Caplet cannot be inserted in the middle of the hierarchy");
// for (Capsule c = sup.cc; c != sup; c = c.sup) {
// if (c.sup == sup)
// c.sup = cc;
// }
// for (Capsule c = cc; c != this; c = c.sup)
// c.cc = sup.cc;
// this.cc = sup.cc;
}
}
}
/**
* Checks whether a caplet with the given class name is installed.
*/
protected final boolean hasCaplet(String name) {
for (Capsule c = cc; c != null; c = c.sup) {
for (Class<?> cls = c.getClass(); cls != null; cls = cls.getSuperclass()) {
if (name.equals(cls.getName()))
return true;
}
}
return false;
}
/**
* The first caplet in the caplet chain starting with the current one and going up (back) that is of the requested type.
*/
protected final <T extends Capsule> T sup(Class<T> caplet) {
for (Capsule c = this; c != null; c = c.sup) {
if (caplet.isInstance(c))
return caplet.cast(c);
}
return null;
}
/**
* The first caplet in the caplet chain starting with the current one and going up (back) that is of the requested type.
*/
protected final Capsule sup(String ClassName) {
for (Capsule c = this; c != null; c = c.sup) {
for (Class cls = c.getClass(); cls != null; cls = cls.getSuperclass()) {
if (ClassName.equals(cls.getName()))
return c;
}
}
return null;
}
// called by utils' Capsule
final List<Class<? extends Capsule>> getCaplets() {
final List<Class<? extends Capsule>> caplets = new ArrayList<>();
for (Capsule c = cc; c != null; c = c.sup)
caplets.add(c.getClass());
Collections.reverse(caplets);
return caplets;
}
protected final <T extends Capsule> T getCallTarget(Class<T> clazz) {
/*
* Here we're implementing both the "invokevirtual" and "invokespecial".
* We want to somehow differentiate the case where the function is called directly -- and should, like invokevirtual, target cc, the
* last caplet in the hieracrchy -- from the case where the function is called with super.foo -- and should, like invokevirtual,
* target sup, the previous caplet in the hierarchy.
*/
Capsule target = null;
if ((sup == null || sup.sup(clazz) == null || this.jarFile != ((Capsule) sup.sup(clazz)).jarFile) && cc != this) { // the jarFile condition tests if this is the first caplet in a wrapper capsule
final StackTraceElement[] st = new Throwable().getStackTrace();
if (st == null || st.length < 3)
throw new AssertionError("No debug information in Capsule class");
final int c1 = 1;
if (!st[c1].getClassName().equals(clazz.getName()))
throw new RuntimeException("Illegal access. Method can only be called by the " + clazz.getName() + " class");
int c2 = 2;
while (isStream(st[c2].getClassName()))
c2++;
if (st[c1].getLineNumber() <= 0 || st[c2].getLineNumber() <= 0)
throw new AssertionError("No debug information in Capsule class");
// we return CC if the caller is also Capsule but not the same method (which would mean this is a sup.foo() call)
if (!st[c2].getMethodName().equals(st[c1].getMethodName())
|| (st[c2].getClassName().equals(clazz.getName()) && Math.abs(st[c2].getLineNumber() - st[c1].getLineNumber()) > 3))
target = cc;
}
if (target == null)
target = sup;