• 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.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