• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.nullness.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 
108   @Nullable
modulePath(String moduleName)109   Path modulePath(String moduleName) {
110     Path path = modulesRoot.resolve(moduleName);
111     return Files.exists(path) ? path : null;
112   }
113 
114   @Nullable
module(String moduleName)115   ModuleInfo module(String moduleName) {
116     ModuleInfo result = moduleMap.get(moduleName);
117     if (result == null) {
118       Path path = modulePath(moduleName);
119       if (path == null) {
120         return null;
121       }
122       path = path.resolve("module-info.class");
123       result = BytecodeBinder.bindModuleInfo(path.toString(), toByteArrayOrDie(path));
124       moduleMap.put(moduleName, result);
125     }
126     return result;
127   }
128 
initPackage(String packageName)129   boolean initPackage(String packageName) {
130     Collection<String> moduleNames = packageMap.get(packageName);
131     if (moduleNames.isEmpty()) {
132       return false;
133     }
134     if (!loadedPackages.add(packageName)) {
135       return true;
136     }
137     Env<ClassSymbol, BytecodeBoundClass> env =
138         new Env<ClassSymbol, BytecodeBoundClass>() {
139           @Override
140           public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
141             return JimageClassBinder.this.env.get(sym);
142           }
143         };
144     for (String moduleName : moduleNames) {
145       if (moduleName != null) {
146         // TODO(cushon): is this requireNonNull safe?
147         Path modulePath = requireNonNull(modulePath(moduleName), moduleName);
148         Path modulePackagePath = modulePath.resolve(packageName);
149         try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulePackagePath)) {
150           for (Path path : ds) {
151             if (!Files.isRegularFile(path)
152                 || path.getFileName().toString().equals("module-info.class")) {
153               continue;
154             }
155             String binaryName = modulePath.relativize(path).toString();
156             binaryName = binaryName.substring(0, binaryName.length() - ".class".length());
157             ClassSymbol sym = new ClassSymbol(binaryName);
158             packageClassesBySimpleName.put(packageName, sym.simpleName(), sym);
159             JimageClassBinder.this.env.put(
160                 sym, new BytecodeBoundClass(sym, toByteArrayOrDie(path), env, path.toString()));
161           }
162         } catch (IOException e) {
163           throw new UncheckedIOException(e);
164         }
165       }
166     }
167     return true;
168   }
169 
toByteArrayOrDie(Path path)170   private static Supplier<byte[]> toByteArrayOrDie(Path path) {
171     return Suppliers.memoize(
172         new Supplier<byte[]>() {
173           @Override
174           public byte[] get() {
175             try {
176               return Files.readAllBytes(path);
177             } catch (IOException e) {
178               throw new UncheckedIOException(e);
179             }
180           }
181         });
182   }
183 
184   private class JimageTopLevelIndex implements TopLevelIndex {
185 
186     final Scope topLevelScope =
187         new Scope() {
188           @Override
189           public @Nullable LookupResult lookup(LookupKey lookupKey) {
190             // Find the longest prefix of the key that corresponds to a package name.
191             // TODO(cushon): SimpleTopLevelIndex uses a prefix map for this, does it matter?
192             Scope scope = null;
193             ImmutableList<Ident> names = lookupKey.simpleNames();
194             ImmutableList.Builder<String> flatNamesBuilder = ImmutableList.builder();
195             for (Ident name : names) {
196               flatNamesBuilder.add(name.value());
197             }
198             ImmutableList<String> flatNames = flatNamesBuilder.build();
199             int idx = -1;
200             for (int i = 1; i < names.size(); i++) {
201               Scope cand = lookupPackage(flatNames.subList(0, i));
202               if (cand != null) {
203                 scope = cand;
204                 idx = i;
205               }
206             }
207             return scope != null
208                 ? scope.lookup(new LookupKey(names.subList(idx, names.size())))
209                 : null;
210           }
211         };
212 
213     @Override
214     public Scope scope() {
215       return topLevelScope;
216     }
217 
218     @Override
219     public @Nullable PackageScope lookupPackage(Iterable<String> name) {
220       String packageName = Joiner.on('/').join(name);
221       if (!initPackage(packageName)) {
222         return null;
223       }
224       return new PackageScope() {
225         @Override
226         public @Nullable LookupResult lookup(LookupKey lookupKey) {
227           ClassSymbol sym = packageClassesBySimpleName.get(packageName, lookupKey.first().value());
228           return sym != null ? new LookupResult(sym, lookupKey) : null;
229         }
230 
231         @Override
232         public Iterable<ClassSymbol> classes() {
233           return packageClassesBySimpleName.row(packageName).values();
234         }
235       };
236     }
237   }
238 
239   private class JimageClassPath implements ClassPath {
240 
241     final TopLevelIndex index = new JimageTopLevelIndex();
242 
243     @Override
244     public Env<ClassSymbol, BytecodeBoundClass> env() {
245       return new Env<ClassSymbol, BytecodeBoundClass>() {
246         @Override
247         public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
248           return initPackage(sym.packageName()) ? env.get(sym) : null;
249         }
250       };
251     }
252 
253     @Override
254     public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
255       return new Env<ModuleSymbol, ModuleInfo>() {
256         @Override
257         public @Nullable ModuleInfo get(ModuleSymbol module) {
258           return module(module.name());
259         }
260       };
261     }
262 
263     @Override
264     public TopLevelIndex index() {
265       return index;
266     }
267 
268     @Override
269     public @Nullable Supplier<byte[]> resource(String input) {
270       return null;
271     }
272   }
273 }
274