1/* 2 * Copyright (c) 2016, 2017, 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 */ 25package jdk.jshell.execution; 26 27import java.io.ByteArrayInputStream; 28import java.io.File; 29import java.io.IOException; 30import java.io.InputStream; 31import java.net.MalformedURLException; 32import java.net.URI; 33import java.net.URISyntaxException; 34import java.net.URL; 35import java.net.URLClassLoader; 36import java.net.URLConnection; 37import java.net.URLStreamHandler; 38import java.security.CodeSource; 39import java.time.Instant; 40import java.time.ZoneId; 41import java.time.ZonedDateTime; 42import java.time.format.DateTimeFormatter; 43import java.util.ArrayList; 44import java.util.Collections; 45import java.util.Date; 46import java.util.Enumeration; 47import java.util.HashMap; 48import java.util.LinkedHashMap; 49import java.util.List; 50import java.util.Map; 51 52import jdk.jshell.spi.ExecutionControl.ClassBytecodes; 53import jdk.jshell.spi.ExecutionControl.ClassInstallException; 54import jdk.jshell.spi.ExecutionControl.EngineTerminationException; 55import jdk.jshell.spi.ExecutionControl.InternalException; 56 57/** 58 * The standard implementation of {@link LoaderDelegate} using 59 * a {@link URLClassLoader}. 60 * 61 * @author Robert Field 62 */ 63class DefaultLoaderDelegate implements LoaderDelegate { 64 65 private final RemoteClassLoader loader; 66 private final Map<String, Class<?>> klasses = new HashMap<>(); 67 68 private static class RemoteClassLoader extends URLClassLoader { 69 70 private final Map<String, ClassFile> classFiles = new HashMap<>(); 71 72 RemoteClassLoader() { 73 super(new URL[0]); 74 } 75 76 private class ResourceURLStreamHandler extends URLStreamHandler { 77 78 private final String name; 79 80 ResourceURLStreamHandler(String name) { 81 this.name = name; 82 } 83 84 @Override 85 protected URLConnection openConnection(URL u) throws IOException { 86 return new URLConnection(u) { 87 private InputStream in; 88 private Map<String, List<String>> fields; 89 private List<String> fieldNames; 90 91 @Override 92 public void connect() { 93 if (connected) { 94 return; 95 } 96 connected = true; 97 ClassFile file = classFiles.get(name); 98 in = new ByteArrayInputStream(file.data); 99 fields = new LinkedHashMap<>(); 100 fields.put("content-length", List.of(Integer.toString(file.data.length))); 101 Instant instant = new Date(file.timestamp).toInstant(); 102 ZonedDateTime time = ZonedDateTime.ofInstant(instant, ZoneId.of("GMT")); 103 String timeStamp = DateTimeFormatter.RFC_1123_DATE_TIME.format(time); 104 fields.put("date", List.of(timeStamp)); 105 fields.put("last-modified", List.of(timeStamp)); 106 fieldNames = new ArrayList<>(fields.keySet()); 107 } 108 109 @Override 110 public InputStream getInputStream() throws IOException { 111 connect(); 112 return in; 113 } 114 115 @Override 116 public String getHeaderField(String name) { 117 connect(); 118 return fields.getOrDefault(name, List.of()) 119 .stream() 120 .findFirst() 121 .orElse(null); 122 } 123 124 @Override 125 public Map<String, List<String>> getHeaderFields() { 126 connect(); 127 return fields; 128 } 129 130 @Override 131 public String getHeaderFieldKey(int n) { 132 return n < fieldNames.size() ? fieldNames.get(n) : null; 133 } 134 135 @Override 136 public String getHeaderField(int n) { 137 String name = getHeaderFieldKey(n); 138 139 return name != null ? getHeaderField(name) : null; 140 } 141 142 }; 143 } 144 } 145 146 void declare(String name, byte[] bytes) { 147 classFiles.put(toResourceString(name), new ClassFile(bytes, System.currentTimeMillis())); 148 } 149 150 @Override 151 protected Class<?> findClass(String name) throws ClassNotFoundException { 152 ClassFile file = classFiles.get(toResourceString(name)); 153 if (file == null) { 154 return super.findClass(name); 155 } 156 return super.defineClass(name, file.data, 0, file.data.length, (CodeSource) null); 157 } 158 159 @Override 160 public URL findResource(String name) { 161 URL u = doFindResource(name); 162 return u != null ? u : super.findResource(name); 163 } 164 165 @Override 166 public Enumeration<URL> findResources(String name) throws IOException { 167 URL u = doFindResource(name); 168 Enumeration<URL> sup = super.findResources(name); 169 170 if (u == null) { 171 return sup; 172 } 173 174 List<URL> result = new ArrayList<>(); 175 176 while (sup.hasMoreElements()) { 177 result.add(sup.nextElement()); 178 } 179 180 result.add(u); 181 182 return Collections.enumeration(result); 183 } 184 185 private URL doFindResource(String name) { 186 if (classFiles.containsKey(name)) { 187 try { 188 return new URL(null, 189 new URI("jshell", null, "/" + name, null).toString(), 190 new ResourceURLStreamHandler(name)); 191 } catch (MalformedURLException | URISyntaxException ex) { 192 throw new InternalError(ex); 193 } 194 } 195 196 return null; 197 } 198 199 private String toResourceString(String className) { 200 return className.replace('.', '/') + ".class"; 201 } 202 203 @Override 204 public void addURL(URL url) { 205 super.addURL(url); 206 } 207 208 private static class ClassFile { 209 public final byte[] data; 210 public final long timestamp; 211 212 ClassFile(byte[] data, long timestamp) { 213 this.data = data; 214 this.timestamp = timestamp; 215 } 216 217 } 218 } 219 220 public DefaultLoaderDelegate() { 221 this.loader = new RemoteClassLoader(); 222 Thread.currentThread().setContextClassLoader(loader); 223 } 224 225 @Override 226 public void load(ClassBytecodes[] cbcs) 227 throws ClassInstallException, EngineTerminationException { 228 boolean[] loaded = new boolean[cbcs.length]; 229 try { 230 for (ClassBytecodes cbc : cbcs) { 231 loader.declare(cbc.name(), cbc.bytecodes()); 232 } 233 for (int i = 0; i < cbcs.length; ++i) { 234 ClassBytecodes cbc = cbcs[i]; 235 Class<?> klass = loader.loadClass(cbc.name()); 236 klasses.put(cbc.name(), klass); 237 loaded[i] = true; 238 // Get class loaded to the point of, at least, preparation 239 klass.getDeclaredMethods(); 240 } 241 } catch (Throwable ex) { 242 throw new ClassInstallException("load: " + ex.getMessage(), loaded); 243 } 244 } 245 246 @Override 247 public void classesRedefined(ClassBytecodes[] cbcs) { 248 for (ClassBytecodes cbc : cbcs) { 249 loader.declare(cbc.name(), cbc.bytecodes()); 250 } 251 } 252 253 @Override 254 public void addToClasspath(String cp) 255 throws EngineTerminationException, InternalException { 256 try { 257 for (String path : cp.split(File.pathSeparator)) { 258 loader.addURL(new File(path).toURI().toURL()); 259 } 260 } catch (Exception ex) { 261 throw new InternalException(ex.toString()); 262 } 263 } 264 265 @Override 266 public Class<?> findClass(String name) throws ClassNotFoundException { 267 Class<?> klass = klasses.get(name); 268 if (klass == null) { 269 throw new ClassNotFoundException(name + " not found"); 270 } else { 271 return klass; 272 } 273 } 274 275} 276