1 /*
2 * Copyright (c) 2014, 2025, 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 */
25
26 package com.sun.tools.javac.platform;
27
28 import java.io.IOException;
29 import java.io.PrintWriter;
30 import java.nio.file.DirectoryStream;
31 import java.nio.file.FileSystem;
32 import java.nio.file.FileSystems;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.ProviderNotFoundException;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.EnumSet;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Map.Entry;
47 import java.util.NoSuchElementException;
48 import java.util.Set;
49 import java.util.TreeSet;
50
51 import javax.annotation.processing.Processor;
52 import javax.tools.ForwardingJavaFileObject;
53 import javax.tools.JavaFileManager;
54 import javax.tools.JavaFileObject;
55 import javax.tools.JavaFileObject.Kind;
56 import javax.tools.StandardLocation;
57
58 import com.sun.source.util.Plugin;
59 import com.sun.tools.javac.code.Source;
60 import com.sun.tools.javac.code.Source.Feature;
61 import com.sun.tools.javac.file.CacheFSInfo;
62 import com.sun.tools.javac.file.JavacFileManager;
63 import com.sun.tools.javac.jvm.Target;
64 import com.sun.tools.javac.main.Option;
65 import com.sun.tools.javac.util.Context;
66 import com.sun.tools.javac.util.Log;
67 import com.sun.tools.javac.util.StringUtils;
68
69 /** PlatformProvider for JDK N.
70 *
71 * <p><b>This is NOT part of any supported API.
72 * If you write code that depends on this, you do so at your own risk.
73 * This code and its internal interfaces are subject to change or
74 * deletion without notice.</b>
75 */
76 public class JDKPlatformProvider implements PlatformProvider {
77
78 public static final String PREVIEW_OPTION = "preview";
79 private static final String CT_SYM_PREVIEW_VERSION = "@";
80
81 @Override
82 public Iterable<String> getSupportedPlatformNames() {
83 return SUPPORTED_JAVA_PLATFORM_VERSIONS;
84 }
85
86 @Override
87 public PlatformDescription getPlatform(String platformName, String options) throws PlatformNotSupported {
88 if (!SUPPORTED_JAVA_PLATFORM_VERSIONS.contains(platformName)) {
89 throw new PlatformNotSupported();
90 }
91 if (PREVIEW_OPTION.equals(options) && !Source.DEFAULT.name.equals(platformName)) {
92 throw new PlatformNotSupported();
93 }
94 return getPlatformTrusted(platformName, options);
95 }
96
97 public PlatformDescription getPlatformTrusted(String platformName, String options) {
98 String ctSymVersion;
99
100 if (PREVIEW_OPTION.equals(options)) {
101 ctSymVersion = CT_SYM_PREVIEW_VERSION;
102 } else {
103 ctSymVersion =
104 StringUtils.toUpperCase(Integer.toString(Integer.parseInt(platformName), Character.MAX_RADIX));
105 }
106
107 return new PlatformDescriptionImpl(platformName, ctSymVersion);
108 }
109
110 private static final String[] symbolFileLocation = { "lib", "ct.sym" };
111
112 // These must match attributes defined in ZipFileSystem.java.
113 private static final Map<String, ?> CT_SYM_ZIP_ENV = Map.of(
114 // Symbol file should always be opened read-only.
115 "accessMode", "readOnly",
116 // Uses less accurate, but faster, timestamp information
117 // (nobody should care about timestamps in the CT symbol file).
118 "zipinfo-time", "false");
119
120 private static final Set<String> SUPPORTED_JAVA_PLATFORM_VERSIONS;
121 public static final Comparator<String> NUMERICAL_COMPARATOR = (s1, s2) -> {
122 int i1;
123 try {
124 i1 = Integer.parseInt(s1);
125 } catch (NumberFormatException ex) {
126 i1 = Integer.MAX_VALUE;
127 }
128 int i2;
129 try {
130 i2 = Integer.parseInt(s2);
131 } catch (NumberFormatException ex) {
132 i2 = Integer.MAX_VALUE;
133 }
134 return i1 != i2 ? i1 - i2 : s1.compareTo(s2);
135 };
136
137 static {
138 SUPPORTED_JAVA_PLATFORM_VERSIONS = new TreeSet<>(NUMERICAL_COMPARATOR);
139 Path ctSymFile = findCtSym();
140 if (Files.exists(ctSymFile)) {
141 try (FileSystem fs = FileSystems.newFileSystem(ctSymFile, CT_SYM_ZIP_ENV);
142 DirectoryStream<Path> dir =
143 Files.newDirectoryStream(fs.getRootDirectories().iterator().next())) {
144 for (Path section : dir) {
145 if (section.getFileName().toString().contains("-"))
146 continue;
147 for (char ver : section.getFileName().toString().toCharArray()) {
148 String verString = Character.toString(ver);
149
150 if (CT_SYM_PREVIEW_VERSION.equals(verString)) {
151 continue; //ignore - preview is just an option
152 }
153
154 Target t = Target.lookup("" + Integer.parseInt(verString, Character.MAX_RADIX));
155
156 if (t != null) {
157 SUPPORTED_JAVA_PLATFORM_VERSIONS.add(targetNumericVersion(t));
158 }
159 }
160 }
161 } catch (IOException | ProviderNotFoundException ex) {
162 }
163 }
164 }
165
166 private static String targetNumericVersion(Target target) {
167 return Integer.toString(target.ordinal() - Target.JDK1_1.ordinal() + 1);
168 }
169
170 static class PlatformDescriptionImpl implements PlatformDescription {
171
172 private final Map<Path, FileSystem> ctSym2FileSystem = new HashMap<>();
173 private final String sourceVersion;
174 private final String ctSymVersion;
175
176 PlatformDescriptionImpl(String sourceVersion, String ctSymVersion) {
177 this.sourceVersion = sourceVersion;
178 this.ctSymVersion = ctSymVersion;
179 }
180
181 @Override
182 public JavaFileManager getFileManager() {
183 Context context = new Context();
184 PrintWriter pw = new PrintWriter(System.err, true);
185 context.put(Log.errKey, pw);
186 CacheFSInfo.preRegister(context);
187 JavacFileManager fm = new JavacFileManager(context, true, null) {
188 @Override
189 public boolean hasLocation(Location location) {
190 return super.hasExplicitLocation(location);
191 }
192
193 @Override
194 public JavaFileObject getJavaFileForInput(Location location, String className,
195 Kind kind) throws IOException {
196 if (kind == Kind.CLASS) {
197 String fileName = className.replace('.', '/');
198 JavaFileObject result =
199 (JavaFileObject) getFileForInput(location,
200 "",
201 fileName + ".sig");
202
203 if (result != null) {
204 return new SigJavaFileObject(result);
205 }
206 }
207
208 return super.getJavaFileForInput(location, className, kind);
209 }
210
211 @Override
212 public Iterable<JavaFileObject> list(Location location,
213 String packageName,
214 Set<Kind> kinds,
215 boolean recurse) throws IOException {
216 Set<Kind> enhancedKinds = EnumSet.copyOf(kinds);
217
218 enhancedKinds.add(Kind.OTHER);
219
220 Iterable<JavaFileObject> listed = super.list(location, packageName,
221 enhancedKinds, recurse);
222
223 return () -> new Iterator<JavaFileObject>() {
224 private final Iterator<JavaFileObject> original = listed.iterator();
225 private JavaFileObject next;
226 @Override
227 public boolean hasNext() {
228 if (next == null) {
229 while (original.hasNext()) {
230 JavaFileObject fo = original.next();
231
232 if (fo.getKind() == Kind.OTHER &&
233 fo.getName().endsWith(".sig")) {
234 next = new SigJavaFileObject(fo);
235 break;
236 }
237
238 if (kinds.contains(fo.getKind())) {
239 next = fo;
240 break;
241 }
242 }
243 }
244 return next != null;
245 }
246
247 @Override
248 public JavaFileObject next() {
249 if (!hasNext())
250 throw new NoSuchElementException();
251 JavaFileObject result = next;
252 next = null;
253 return result;
254 }
255
256 };
257 }
258
259 @Override
260 public String inferBinaryName(Location location, JavaFileObject file) {
261 if (file instanceof SigJavaFileObject sigJavaFileObject) {
262 file = sigJavaFileObject.getDelegate();
263 }
264 return super.inferBinaryName(location, file);
265 }
266
267 };
268
269 fm.handleOption(Option.MULTIRELEASE, sourceVersion);
270
271 Path file = findCtSym();
272 // file == ${jdk.home}/lib/ct.sym
273 if (Files.exists(file)) {
274 try {
275 FileSystem fs = ctSym2FileSystem.get(file);
276 if (fs == null) {
277 fs = FileSystems.newFileSystem(file, CT_SYM_ZIP_ENV);
278 // If for any reason this was not opened from a ZIP file,
279 // then the resulting file system would not be read-only.
280 // NOTE: This check is disabled until JDK 25 bootstrap!
281 // Assert.check(fs.isReadOnly());
282 ctSym2FileSystem.put(file, fs);
283 }
284
285 Path root = fs.getRootDirectories().iterator().next();
286 boolean hasModules =
287 Feature.MODULES.allowedInSource(Source.lookup(sourceVersion));
288
289 if (!hasModules) {
290 List<Path> paths = new ArrayList<>();
291
292 try (DirectoryStream<Path> dir = Files.newDirectoryStream(root)) {
293 for (Path section : dir) {
294 if (section.getFileName().toString().contains(ctSymVersion) &&
295 !section.getFileName().toString().contains("-")) {
296 try (DirectoryStream<Path> modules = Files.newDirectoryStream(section)) {
297 for (Path module : modules) {
298 paths.add(module);
299 }
300 }
301 }
302 }
303 }
304
305 fm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, paths);
306 } else {
307 Map<String, List<Path>> module2Paths = new HashMap<>();
308
309 try (DirectoryStream<Path> dir = Files.newDirectoryStream(root)) {
310 for (Path section : dir) {
311 if (section.getFileName().toString().contains(ctSymVersion) &&
312 !section.getFileName().toString().contains("-")) {
313 try (DirectoryStream<Path> modules = Files.newDirectoryStream(section)) {
314 for (Path module : modules) {
315 module2Paths.computeIfAbsent(module.getFileName().toString(), dummy -> new ArrayList<>()).add(module);
316 }
317 }
318 }
319 }
320 }
321
322 fm.handleOption("--system", Arrays.asList("none").iterator());
323
324 for (Entry<String, List<Path>> e : module2Paths.entrySet()) {
325 fm.setLocationForModule(StandardLocation.SYSTEM_MODULES,
326 e.getKey(),
327 e.getValue());
328 }
329 }
330
331 return fm;
332 } catch (IOException ex) {
333 throw new IllegalStateException(ex);
334 }
335 } else {
336 throw new IllegalStateException("Cannot find ct.sym!");
337 }
338 }
339
340 private static class SigJavaFileObject extends ForwardingJavaFileObject<JavaFileObject> {
341
342 public SigJavaFileObject(JavaFileObject fileObject) {
343 super(fileObject);
344 }
345
346 @Override
347 public Kind getKind() {
348 return Kind.CLASS;
349 }
350
351 @Override
352 public boolean isNameCompatible(String simpleName, Kind kind) {
353 return super.isNameCompatible(simpleName + ".sig", Kind.OTHER);
354 }
355
356 public JavaFileObject getDelegate() {
357 return fileObject;
358 }
359 }
360
361 @Override
362 public String getSourceVersion() {
363 return sourceVersion;
364 }
365
366 @Override
367 public String getTargetVersion() {
368 return sourceVersion;
369 }
370
371 @Override
372 public List<PluginInfo<Processor>> getAnnotationProcessors() {
373 return Collections.emptyList();
374 }
375
376 @Override
377 public List<PluginInfo<Plugin>> getPlugins() {
378 return Collections.emptyList();
379 }
380
381 @Override
382 public List<String> getAdditionalOptions() {
383 return Collections.emptyList();
384 }
385
386 @Override
387 public void close() throws IOException {
388 for (FileSystem fs : ctSym2FileSystem.values()) {
389 fs.close();
390 }
391 ctSym2FileSystem.clear();
392 }
393
394 }
395
396 static Path findCtSym() {
397 String javaHome = System.getProperty("java.home");
398 Path file = Paths.get(javaHome);
399 // file == ${jdk.home}
400 for (String name : symbolFileLocation)
401 file = file.resolve(name);
402 return file;
403 }
404
405 }