]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.jshell/src/org/argeo/internal/cms/jshell/osgi/WrappingLoaderDelegate.java
Merge tag 'v2.3.23' into testing
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / internal / cms / jshell / osgi / WrappingLoaderDelegate.java
1 package org.argeo.internal.cms.jshell.osgi;
2
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;
8 import java.net.URI;
9 import java.net.URISyntaxException;
10 import java.net.URL;
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;
26 import java.util.Map;
27
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;
33
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<>();
38
39 private final ExecutionEnv env;
40
41 public WrappingLoaderDelegate(ExecutionEnv env, ClassLoader parentClassLoader) {
42 this.env = env;
43 this.loader = new WrappingClassloader(parentClassLoader);
44
45 Thread.currentThread().setContextClassLoader(loader);
46 }
47
48 @Override
49 public void load(ClassBytecodes[] cbcs) throws ClassInstallException, EngineTerminationException {
50 boolean[] loaded = new boolean[cbcs.length];
51 try {
52 for (ClassBytecodes cbc : cbcs) {
53 loader.declare(cbc.name(), cbc.bytecodes());
54 }
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);
59 loaded[i] = true;
60 // Get class loaded to the point of, at least, preparation
61 klass.getDeclaredMethods();
62 }
63 } catch (Throwable ex) {
64 throw new ClassInstallException("load: " + ex.getMessage(), loaded);
65 }
66 }
67
68 @Override
69 public void classesRedefined(ClassBytecodes[] cbcs) {
70 for (ClassBytecodes cbc : cbcs) {
71 loader.declare(cbc.name(), cbc.bytecodes());
72 }
73 }
74
75 @Override
76 public void addToClasspath(String cp) {
77 // ignore
78 }
79
80 @Override
81 public Class<?> findClass(String name) throws ClassNotFoundException {
82 Class<?> klass = klasses.get(name);
83 if (klass == null) {
84 throw new ClassNotFoundException(name + " not found");
85 } else {
86 return klass;
87 }
88 }
89
90 private class WrappingClassloader extends SecureClassLoader implements ExecutionEnv {
91
92 private final Map<String, ClassFile> classFiles = new HashMap<>();
93
94 public WrappingClassloader(ClassLoader parent) {
95 super(parent);
96 }
97
98 private class ResourceURLStreamHandler extends URLStreamHandler {
99
100 private final String name;
101
102 ResourceURLStreamHandler(String name) {
103 this.name = name;
104 }
105
106 @Override
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;
112
113 @Override
114 public void connect() {
115 if (connected) {
116 return;
117 }
118 connected = true;
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());
129 }
130
131 @Override
132 public InputStream getInputStream() throws IOException {
133 connect();
134 return in;
135 }
136
137 @Override
138 public String getHeaderField(String name) {
139 connect();
140 return fields.getOrDefault(name, List.of()).stream().findFirst().orElse(null);
141 }
142
143 @Override
144 public Map<String, List<String>> getHeaderFields() {
145 connect();
146 return fields;
147 }
148
149 @Override
150 public String getHeaderFieldKey(int n) {
151 return n < fieldNames.size() ? fieldNames.get(n) : null;
152 }
153
154 @Override
155 public String getHeaderField(int n) {
156 String name = getHeaderFieldKey(n);
157
158 return name != null ? getHeaderField(name) : null;
159 }
160
161 };
162 }
163 }
164
165 void declare(String name, byte[] bytes) {
166 classFiles.put(toResourceString(name), new ClassFile(bytes, System.currentTimeMillis()));
167 }
168
169 @Override
170 protected Class<?> findClass(String name) throws ClassNotFoundException {
171 ClassFile file = classFiles.get(toResourceString(name));
172 if (file == null) {
173 return super.findClass(name);
174 }
175 return super.defineClass(name, file.data, 0, file.data.length, (CodeSource) null);
176 }
177
178 @Override
179 public URL findResource(String name) {
180 URL u = doFindResource(name);
181 return u != null ? u : super.findResource(name);
182 }
183
184 @Override
185 public Enumeration<URL> findResources(String name) throws IOException {
186 URL u = doFindResource(name);
187 Enumeration<URL> sup = super.findResources(name);
188
189 if (u == null) {
190 return sup;
191 }
192
193 List<URL> result = new ArrayList<>();
194
195 while (sup.hasMoreElements()) {
196 result.add(sup.nextElement());
197 }
198
199 result.add(u);
200
201 return Collections.enumeration(result);
202 }
203
204 private URL doFindResource(String name) {
205 if (classFiles.containsKey(name)) {
206 try {
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);
211 }
212 }
213
214 return null;
215 }
216
217 private String toResourceString(String className) {
218 return className.replace('.', '/') + ".class";
219 }
220
221 private static class ClassFile {
222 public final byte[] data;
223 public final long timestamp;
224
225 ClassFile(byte[] data, long timestamp) {
226 this.data = data;
227 this.timestamp = timestamp;
228 }
229
230 }
231
232 @Override
233 public InputStream userIn() {
234 return env.userIn();
235 }
236
237 @Override
238 public PrintStream userOut() {
239 return env.userOut();
240 }
241
242 @Override
243 public PrintStream userErr() {
244 return env.userErr();
245 }
246
247 @Override
248 public List<String> extraRemoteVMOptions() {
249 return env.extraRemoteVMOptions();
250 }
251
252 @Override
253 public void closeDown() {
254 // env.closeDown();
255 }
256
257 }
258
259 }