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