1 /* 2 * Copyright 2018 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.turbine.binder; 18 19 import static com.google.common.base.StandardSystemProperty.JAVA_HOME; 20 import static java.util.Objects.requireNonNull; 21 22 import com.google.common.base.Joiner; 23 import com.google.common.base.Supplier; 24 import com.google.common.base.Suppliers; 25 import com.google.common.collect.HashBasedTable; 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.collect.ImmutableMap; 28 import com.google.common.collect.ImmutableMultimap; 29 import com.google.common.collect.Multimap; 30 import com.google.common.collect.Table; 31 import com.google.turbine.binder.bound.ModuleInfo; 32 import com.google.turbine.binder.bytecode.BytecodeBinder; 33 import com.google.turbine.binder.bytecode.BytecodeBoundClass; 34 import com.google.turbine.binder.env.Env; 35 import com.google.turbine.binder.lookup.LookupKey; 36 import com.google.turbine.binder.lookup.LookupResult; 37 import com.google.turbine.binder.lookup.PackageScope; 38 import com.google.turbine.binder.lookup.Scope; 39 import com.google.turbine.binder.lookup.TopLevelIndex; 40 import com.google.turbine.binder.sym.ClassSymbol; 41 import com.google.turbine.binder.sym.ModuleSymbol; 42 import com.google.turbine.tree.Tree.Ident; 43 import java.io.IOException; 44 import java.io.UncheckedIOException; 45 import java.net.URI; 46 import java.nio.file.DirectoryStream; 47 import java.nio.file.FileSystem; 48 import java.nio.file.FileSystems; 49 import java.nio.file.Files; 50 import java.nio.file.Path; 51 import java.util.Collection; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.Map; 55 import java.util.Set; 56 import org.jspecify.annotations.Nullable; 57 58 /** Constructs a platform {@link ClassPath} from the current JDK's jimage file using jrtfs. */ 59 public class JimageClassBinder { 60 create(FileSystem fileSystem)61 static JimageClassBinder create(FileSystem fileSystem) throws IOException { 62 Path modules = fileSystem.getPath("/modules"); 63 Path packages = fileSystem.getPath("/packages"); 64 ImmutableMultimap.Builder<String, String> packageMap = ImmutableMultimap.builder(); 65 try (DirectoryStream<Path> ps = Files.newDirectoryStream(packages)) { 66 for (Path p : ps) { 67 String packageName = packages.relativize(p).toString().replace('.', '/'); 68 try (DirectoryStream<Path> ms = Files.newDirectoryStream(p)) { 69 for (Path m : ms) { 70 packageMap.put(packageName, p.relativize(m).toString()); 71 } 72 } 73 } 74 } 75 return new JimageClassBinder(packageMap.build(), modules); 76 } 77 78 /** Returns a platform classpath for the host JDK's jimage file. */ bindDefault()79 public static ClassPath bindDefault() throws IOException { 80 return JimageClassBinder.create(FileSystems.getFileSystem(URI.create("jrt:/"))) 81 .new JimageClassPath(); 82 } 83 84 /** Returns a platform classpath for the given JDK's jimage file. */ bind(String javaHome)85 public static ClassPath bind(String javaHome) throws IOException { 86 if (javaHome.equals(JAVA_HOME.value())) { 87 return bindDefault(); 88 } 89 FileSystem fileSystem = 90 FileSystems.newFileSystem(URI.create("jrt:/"), ImmutableMap.of("java.home", javaHome)); 91 return JimageClassBinder.create(fileSystem).new JimageClassPath(); 92 } 93 94 private final Multimap<String, String> packageMap; 95 private final Path modulesRoot; 96 97 private final Set<String> loadedPackages = new HashSet<>(); 98 private final Table<String, String, ClassSymbol> packageClassesBySimpleName = 99 HashBasedTable.create(); 100 private final Map<String, ModuleInfo> moduleMap = new HashMap<>(); 101 private final Map<ClassSymbol, BytecodeBoundClass> env = new HashMap<>(); 102 JimageClassBinder(ImmutableMultimap<String, String> packageMap, Path modules)103 public JimageClassBinder(ImmutableMultimap<String, String> packageMap, Path modules) { 104 this.packageMap = packageMap; 105 this.modulesRoot = modules; 106 } 107 modulePath(String moduleName)108 @Nullable Path modulePath(String moduleName) { 109 Path path = modulesRoot.resolve(moduleName); 110 return Files.exists(path) ? path : null; 111 } 112 module(String moduleName)113 @Nullable ModuleInfo module(String moduleName) { 114 ModuleInfo result = moduleMap.get(moduleName); 115 if (result == null) { 116 Path path = modulePath(moduleName); 117 if (path == null) { 118 return null; 119 } 120 path = path.resolve("module-info.class"); 121 result = BytecodeBinder.bindModuleInfo(path.toString(), toByteArrayOrDie(path)); 122 moduleMap.put(moduleName, result); 123 } 124 return result; 125 } 126 initPackage(String packageName)127 boolean initPackage(String packageName) { 128 Collection<String> moduleNames = packageMap.get(packageName); 129 if (moduleNames.isEmpty()) { 130 return false; 131 } 132 if (!loadedPackages.add(packageName)) { 133 return true; 134 } 135 Env<ClassSymbol, BytecodeBoundClass> env = 136 new Env<ClassSymbol, BytecodeBoundClass>() { 137 @Override 138 public @Nullable BytecodeBoundClass get(ClassSymbol sym) { 139 return JimageClassBinder.this.env.get(sym); 140 } 141 }; 142 for (String moduleName : moduleNames) { 143 if (moduleName != null) { 144 // TODO(cushon): is this requireNonNull safe? 145 Path modulePath = requireNonNull(modulePath(moduleName), moduleName); 146 Path modulePackagePath = modulePath.resolve(packageName); 147 try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulePackagePath)) { 148 for (Path path : ds) { 149 if (!Files.isRegularFile(path) 150 || path.getFileName().toString().equals("module-info.class")) { 151 continue; 152 } 153 String binaryName = modulePath.relativize(path).toString(); 154 binaryName = binaryName.substring(0, binaryName.length() - ".class".length()); 155 ClassSymbol sym = new ClassSymbol(binaryName); 156 packageClassesBySimpleName.put(packageName, sym.simpleName(), sym); 157 JimageClassBinder.this.env.put( 158 sym, new BytecodeBoundClass(sym, toByteArrayOrDie(path), env, path.toString())); 159 } 160 } catch (IOException e) { 161 throw new UncheckedIOException(e); 162 } 163 } 164 } 165 return true; 166 } 167 toByteArrayOrDie(Path path)168 private static Supplier<byte[]> toByteArrayOrDie(Path path) { 169 return Suppliers.memoize( 170 new Supplier<byte[]>() { 171 @Override 172 public byte[] get() { 173 try { 174 return Files.readAllBytes(path); 175 } catch (IOException e) { 176 throw new UncheckedIOException(e); 177 } 178 } 179 }); 180 } 181 182 private class JimageTopLevelIndex implements TopLevelIndex { 183 184 final Scope topLevelScope = 185 new Scope() { 186 @Override 187 public @Nullable LookupResult lookup(LookupKey lookupKey) { 188 // Find the longest prefix of the key that corresponds to a package name. 189 // TODO(cushon): SimpleTopLevelIndex uses a prefix map for this, does it matter? 190 Scope scope = null; 191 ImmutableList<Ident> names = lookupKey.simpleNames(); 192 ImmutableList.Builder<String> flatNamesBuilder = ImmutableList.builder(); 193 for (Ident name : names) { 194 flatNamesBuilder.add(name.value()); 195 } 196 ImmutableList<String> flatNames = flatNamesBuilder.build(); 197 int idx = -1; 198 for (int i = 1; i < names.size(); i++) { 199 Scope cand = lookupPackage(flatNames.subList(0, i)); 200 if (cand != null) { 201 scope = cand; 202 idx = i; 203 } 204 } 205 return scope != null 206 ? scope.lookup(new LookupKey(names.subList(idx, names.size()))) 207 : null; 208 } 209 }; 210 211 @Override 212 public Scope scope() { 213 return topLevelScope; 214 } 215 216 @Override 217 public @Nullable PackageScope lookupPackage(Iterable<String> name) { 218 String packageName = Joiner.on('/').join(name); 219 if (!initPackage(packageName)) { 220 return null; 221 } 222 return new PackageScope() { 223 @Override 224 public @Nullable LookupResult lookup(LookupKey lookupKey) { 225 ClassSymbol sym = packageClassesBySimpleName.get(packageName, lookupKey.first().value()); 226 return sym != null ? new LookupResult(sym, lookupKey) : null; 227 } 228 229 @Override 230 public Iterable<ClassSymbol> classes() { 231 return packageClassesBySimpleName.row(packageName).values(); 232 } 233 }; 234 } 235 } 236 237 private class JimageClassPath implements ClassPath { 238 239 final TopLevelIndex index = new JimageTopLevelIndex(); 240 241 @Override 242 public Env<ClassSymbol, BytecodeBoundClass> env() { 243 return new Env<ClassSymbol, BytecodeBoundClass>() { 244 @Override 245 public @Nullable BytecodeBoundClass get(ClassSymbol sym) { 246 return initPackage(sym.packageName()) ? env.get(sym) : null; 247 } 248 }; 249 } 250 251 @Override 252 public Env<ModuleSymbol, ModuleInfo> moduleEnv() { 253 return new Env<ModuleSymbol, ModuleInfo>() { 254 @Override 255 public @Nullable ModuleInfo get(ModuleSymbol module) { 256 return module(module.name()); 257 } 258 }; 259 } 260 261 @Override 262 public TopLevelIndex index() { 263 return index; 264 } 265 266 @Override 267 public @Nullable Supplier<byte[]> resource(String input) { 268 return null; 269 } 270 } 271 } 272