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 }