11package datadog .trace .bootstrap ;
22
3+ import datadog .trace .bootstrap .instrumentation .api .Pair ;
34import java .io .ByteArrayInputStream ;
45import java .io .File ;
56import java .io .IOException ;
67import java .io .InputStream ;
8+ import java .lang .ref .WeakReference ;
79import java .net .URISyntaxException ;
810import java .net .URL ;
911import java .net .URLConnection ;
1012import java .net .URLStreamHandler ;
11- import java .nio .file .NoSuchFileException ;
1213import java .security .Permission ;
1314import java .util .Enumeration ;
1415import java .util .HashMap ;
16+ import java .util .HashSet ;
1517import java .util .Map ;
18+ import java .util .Set ;
1619import java .util .jar .JarEntry ;
1720import java .util .jar .JarFile ;
1821import lombok .extern .slf4j .Slf4j ;
1922
2023@ Slf4j
2124public class InternalJarURLHandler extends URLStreamHandler {
25+
26+ private static final WeakReference <Pair <String , JarEntry >> NULL = new WeakReference <>(null );
27+
28+ private final String name ;
29+ private final FileNotInInternalJar notFound ;
2230 private final Map <String , JarEntry > filenameToEntry = new HashMap <>();
23- private JarFile bootstrapJarFile ;
31+ private final Set <String > packages = new HashSet <>();
32+ private final JarFile bootstrapJarFile ;
33+
34+ private WeakReference <Pair <String , JarEntry >> cache = NULL ;
2435
2536 InternalJarURLHandler (final String internalJarFileName , final URL bootstrapJarLocation ) {
37+ this .name = internalJarFileName ;
38+ this .notFound = new FileNotInInternalJar (internalJarFileName );
2639 final String filePrefix = internalJarFileName + "/" ;
27-
40+ JarFile jarFile = null ;
41+ String currentDir = "$" ;
2842 try {
2943 if (bootstrapJarLocation != null ) {
30- bootstrapJarFile = new JarFile (new File (bootstrapJarLocation .toURI ()), false );
31- final Enumeration <JarEntry > entries = bootstrapJarFile .entries ();
44+ jarFile = new JarFile (new File (bootstrapJarLocation .toURI ()), false );
45+ final Enumeration <JarEntry > entries = jarFile .entries ();
3246 while (entries .hasMoreElements ()) {
3347 final JarEntry entry = entries .nextElement ();
34-
3548 if (!entry .isDirectory () && entry .getName ().startsWith (filePrefix )) {
3649 String name = entry .getName ();
3750 // remove data suffix
3851 int end = name .endsWith (".classdata" ) ? name .length () - 4 : name .length ();
39- filenameToEntry .put (name .substring (internalJarFileName .length (), end ), entry );
52+ String fileName = name .substring (internalJarFileName .length (), end );
53+ filenameToEntry .put (fileName , entry );
54+ if (fileName .endsWith (".class" ) && !fileName .startsWith (currentDir )) {
55+ currentDir = fileName .substring (1 , fileName .lastIndexOf ('/' ));
56+ String currentPackage = currentDir .replace ('/' , '.' );
57+ packages .add (currentPackage );
58+ }
4059 }
4160 }
4261 }
@@ -47,6 +66,11 @@ public class InternalJarURLHandler extends URLStreamHandler {
4766 if (filenameToEntry .isEmpty ()) {
4867 log .warn ("No internal jar entries found" );
4968 }
69+ this .bootstrapJarFile = jarFile ;
70+ }
71+
72+ Set <String > getPackages () {
73+ return packages ;
5074 }
5175
5276 @ Override
@@ -59,12 +83,26 @@ protected URLConnection openConnection(final URL url) throws IOException {
5983 // nullInputStream() is not available until Java 11
6084 return new InternalJarURLConnection (url , new ByteArrayInputStream (new byte [0 ]));
6185 }
62- final JarEntry entry = filenameToEntry .get (filename );
63- if (null != entry ) {
64- return new InternalJarURLConnection (url , bootstrapJarFile .getInputStream (entry ));
86+ // believe it or not, we're going to get called twice for this,
87+ // and the key will be a new object each time.
88+ Pair <String , JarEntry > pair = cache .get ();
89+ if (null == pair || !filename .equals (pair .getLeft ())) {
90+ final JarEntry entry = filenameToEntry .get (filename );
91+ if (null != entry ) {
92+ pair = Pair .of (filename , entry );
93+ // this mechanism intentionally does not ensure visibility of this write, because it doesn't
94+ // matter
95+ this .cache = new WeakReference <>(pair );
96+ } else {
97+ log .debug ("{} not found in {}" , filename , name );
98+ throw notFound ;
99+ }
65100 } else {
66- throw new NoSuchFileException (url .getFile (), null , url .getFile () + " not in internal jar" );
101+ // hack: just happen to know this only ever happens twice,
102+ // so dismiss cache after a hit
103+ this .cache = NULL ;
67104 }
105+ return new InternalJarURLConnection (url , bootstrapJarFile .getInputStream (pair .getRight ()));
68106 }
69107
70108 private static class InternalJarURLConnection extends URLConnection {
@@ -76,12 +114,12 @@ private InternalJarURLConnection(final URL url, final InputStream inputStream) {
76114 }
77115
78116 @ Override
79- public void connect () throws IOException {
117+ public void connect () {
80118 connected = true ;
81119 }
82120
83121 @ Override
84- public InputStream getInputStream () throws IOException {
122+ public InputStream getInputStream () {
85123 return inputStream ;
86124 }
87125
@@ -91,4 +129,16 @@ public Permission getPermission() {
91129 return null ;
92130 }
93131 }
132+
133+ private static class FileNotInInternalJar extends IOException {
134+
135+ public FileNotInInternalJar (String jarName ) {
136+ super ("class not found in " + jarName );
137+ }
138+
139+ @ Override
140+ public Throwable fillInStackTrace () {
141+ return this ;
142+ }
143+ }
94144}
0 commit comments