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 }