1 package org
.argeo
.internal
.cms
.jshell
.osgi
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
6 import java
.io
.PrintStream
;
7 import java
.net
.MalformedURLException
;
9 import java
.net
.URISyntaxException
;
11 import java
.net
.URLConnection
;
12 import java
.net
.URLStreamHandler
;
13 import java
.security
.CodeSource
;
14 import java
.security
.SecureClassLoader
;
15 import java
.time
.Instant
;
16 import java
.time
.ZoneId
;
17 import java
.time
.ZonedDateTime
;
18 import java
.time
.format
.DateTimeFormatter
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Collections
;
21 import java
.util
.Date
;
22 import java
.util
.Enumeration
;
23 import java
.util
.HashMap
;
24 import java
.util
.LinkedHashMap
;
25 import java
.util
.List
;
28 import jdk
.jshell
.execution
.LoaderDelegate
;
29 import jdk
.jshell
.spi
.ExecutionControl
.ClassBytecodes
;
30 import jdk
.jshell
.spi
.ExecutionControl
.ClassInstallException
;
31 import jdk
.jshell
.spi
.ExecutionControl
.EngineTerminationException
;
32 import jdk
.jshell
.spi
.ExecutionEnv
;
34 /** A {@link LoaderDelegate} using a parent {@link ClassLoader}. */
35 class WrappingLoaderDelegate
implements LoaderDelegate
{
36 private final WrappingClassloader loader
;
37 private final Map
<String
, Class
<?
>> klasses
= new HashMap
<>();
39 private final ExecutionEnv env
;
41 public WrappingLoaderDelegate(ExecutionEnv env
, ClassLoader parentClassLoader
) {
43 this.loader
= new WrappingClassloader(parentClassLoader
);
45 Thread
.currentThread().setContextClassLoader(loader
);
49 public void load(ClassBytecodes
[] cbcs
) throws ClassInstallException
, EngineTerminationException
{
50 boolean[] loaded
= new boolean[cbcs
.length
];
52 for (ClassBytecodes cbc
: cbcs
) {
53 loader
.declare(cbc
.name(), cbc
.bytecodes());
55 for (int i
= 0; i
< cbcs
.length
; ++i
) {
56 ClassBytecodes cbc
= cbcs
[i
];
57 Class
<?
> klass
= loader
.loadClass(cbc
.name());
58 klasses
.put(cbc
.name(), klass
);
60 // Get class loaded to the point of, at least, preparation
61 klass
.getDeclaredMethods();
63 } catch (Throwable ex
) {
64 throw new ClassInstallException("load: " + ex
.getMessage(), loaded
);
69 public void classesRedefined(ClassBytecodes
[] cbcs
) {
70 for (ClassBytecodes cbc
: cbcs
) {
71 loader
.declare(cbc
.name(), cbc
.bytecodes());
76 public void addToClasspath(String cp
) {
81 public Class
<?
> findClass(String name
) throws ClassNotFoundException
{
82 Class
<?
> klass
= klasses
.get(name
);
84 throw new ClassNotFoundException(name
+ " not found");
90 private class WrappingClassloader
extends SecureClassLoader
implements ExecutionEnv
{
92 private final Map
<String
, ClassFile
> classFiles
= new HashMap
<>();
94 public WrappingClassloader(ClassLoader parent
) {
98 private class ResourceURLStreamHandler
extends URLStreamHandler
{
100 private final String name
;
102 ResourceURLStreamHandler(String name
) {
107 protected URLConnection
openConnection(URL u
) throws IOException
{
108 return new URLConnection(u
) {
109 private InputStream in
;
110 private Map
<String
, List
<String
>> fields
;
111 private List
<String
> fieldNames
;
114 public void connect() {
119 ClassFile file
= classFiles
.get(name
);
120 in
= new ByteArrayInputStream(file
.data
);
121 fields
= new LinkedHashMap
<>();
122 fields
.put("content-length", List
.of(Integer
.toString(file
.data
.length
)));
123 Instant instant
= new Date(file
.timestamp
).toInstant();
124 ZonedDateTime time
= ZonedDateTime
.ofInstant(instant
, ZoneId
.of("GMT"));
125 String timeStamp
= DateTimeFormatter
.RFC_1123_DATE_TIME
.format(time
);
126 fields
.put("date", List
.of(timeStamp
));
127 fields
.put("last-modified", List
.of(timeStamp
));
128 fieldNames
= new ArrayList
<>(fields
.keySet());
132 public InputStream
getInputStream() throws IOException
{
138 public String
getHeaderField(String name
) {
140 return fields
.getOrDefault(name
, List
.of()).stream().findFirst().orElse(null);
144 public Map
<String
, List
<String
>> getHeaderFields() {
150 public String
getHeaderFieldKey(int n
) {
151 return n
< fieldNames
.size() ? fieldNames
.get(n
) : null;
155 public String
getHeaderField(int n
) {
156 String name
= getHeaderFieldKey(n
);
158 return name
!= null ?
getHeaderField(name
) : null;
165 void declare(String name
, byte[] bytes
) {
166 classFiles
.put(toResourceString(name
), new ClassFile(bytes
, System
.currentTimeMillis()));
170 protected Class
<?
> findClass(String name
) throws ClassNotFoundException
{
171 ClassFile file
= classFiles
.get(toResourceString(name
));
173 return super.findClass(name
);
175 return super.defineClass(name
, file
.data
, 0, file
.data
.length
, (CodeSource
) null);
179 public URL
findResource(String name
) {
180 URL u
= doFindResource(name
);
181 return u
!= null ? u
: super.findResource(name
);
185 public Enumeration
<URL
> findResources(String name
) throws IOException
{
186 URL u
= doFindResource(name
);
187 Enumeration
<URL
> sup
= super.findResources(name
);
193 List
<URL
> result
= new ArrayList
<>();
195 while (sup
.hasMoreElements()) {
196 result
.add(sup
.nextElement());
201 return Collections
.enumeration(result
);
204 private URL
doFindResource(String name
) {
205 if (classFiles
.containsKey(name
)) {
207 return new URL(null, new URI("jshell", null, "/" + name
, null).toString(),
208 new ResourceURLStreamHandler(name
));
209 } catch (MalformedURLException
| URISyntaxException ex
) {
210 throw new InternalError(ex
);
217 private String
toResourceString(String className
) {
218 return className
.replace('.', '/') + ".class";
221 private static class ClassFile
{
222 public final byte[] data
;
223 public final long timestamp
;
225 ClassFile(byte[] data
, long timestamp
) {
227 this.timestamp
= timestamp
;
233 public InputStream
userIn() {
238 public PrintStream
userOut() {
239 return env
.userOut();
243 public PrintStream
userErr() {
244 return env
.userErr();
248 public List
<String
> extraRemoteVMOptions() {
249 return env
.extraRemoteVMOptions();
253 public void closeDown() {