< prev index next >

src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java

Print this page

  1 /*
  2  * Copyright (c) 2014, 2021, 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.file;
 27 

 28 import java.io.IOException;
 29 import java.io.UncheckedIOException;
 30 import java.lang.ref.SoftReference;
 31 import java.net.URI;
 32 import java.nio.file.DirectoryStream;
 33 import java.nio.file.FileSystem;
 34 import java.nio.file.FileSystems;
 35 import java.nio.file.FileSystemNotFoundException;
 36 import java.nio.file.Files;
 37 import java.nio.file.Path;
 38 import java.nio.file.ProviderNotFoundException;
 39 import java.util.Collections;
 40 import java.util.HashMap;

 41 import java.util.LinkedHashMap;
 42 import java.util.LinkedHashSet;
 43 import java.util.Map;
 44 import java.util.MissingResourceException;
 45 import java.util.ResourceBundle;
 46 import java.util.Set;
 47 
 48 import javax.tools.FileObject;
 49 
 50 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
 51 import com.sun.tools.javac.util.Context;
 52 
 53 /**
 54  * A package-oriented index into the jrt: filesystem.





 55  */
 56 public class JRTIndex {
 57     /** Get a shared instance of the cache. */
 58     private static JRTIndex sharedInstance;
 59     public static synchronized JRTIndex getSharedInstance() {
 60         if (sharedInstance == null) {
 61             try {
 62                 sharedInstance = new JRTIndex();
 63             } catch (IOException e) {
 64                 throw new UncheckedIOException(e);
 65             }












 66         }
 67         return sharedInstance;
 68     }
 69 
 70     /** Get a context-specific instance of a cache. */
 71     public static JRTIndex instance(Context context) {




 72         try {
 73             JRTIndex instance = context.get(JRTIndex.class);
 74             if (instance == null)
 75                 context.put(JRTIndex.class, instance = new JRTIndex());
 76             return instance;
 77         } catch (IOException e) {
 78             throw new UncheckedIOException(e);
 79         }
 80     }
 81 

 82     public static boolean isAvailable() {
 83         try {
 84             FileSystems.getFileSystem(URI.create("jrt:/"));
 85             return true;
 86         } catch (ProviderNotFoundException | FileSystemNotFoundException e) {
 87             return false;
 88         }
 89     }
 90 
 91 
 92     /**
 93      * The jrt: file system.



 94      */
 95     private final FileSystem jrtfs;



 96 
 97     /**
 98      * A lazily evaluated set of entries about the contents of the jrt: file system.
 99      */
100     private final Map<RelativeDirectory, SoftReference<Entry>> entries;



























































































































































101 
102     /**
103      * An entry provides cached info about a specific package directory within jrt:.
104      */
105     class Entry {
106         /**
107          * The regular files for this package.
108          * For now, assume just one instance of each file across all modules.
109          */
110         final Map<String, Path> files;
111 
112         /**
113          * The set of subdirectories in jrt: for this package.
114          */
115         final Set<RelativeDirectory> subdirs;
116 
117         /**
118          * The info that used to be in ct.sym for classes in this package.
119          */
120         final CtSym ctSym;
121 
122         private Entry(Map<String, Path> files, Set<RelativeDirectory> subdirs, CtSym ctSym) {
123             this.files = files;
124             this.subdirs = subdirs;
125             this.ctSym = ctSym;

157                 sb.append("hidden");
158                 needSep = true;
159             }
160             if (proprietary) {
161                 if (needSep) sb.append(",");
162                 sb.append("proprietary");
163                 needSep = true;
164             }
165             if (minProfile != null) {
166                 if (needSep) sb.append(",");
167                 sb.append(minProfile);
168             }
169             sb.append("]");
170             return sb.toString();
171         }
172 
173         static final CtSym EMPTY = new CtSym(false, false, null);
174     }
175 
176     /**
177      * Create and initialize the index.



178      */
179     private JRTIndex() throws IOException {
180         jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
181         entries = new HashMap<>();
182     }
183 





184     public CtSym getCtSym(CharSequence packageName) throws IOException {
185         return getEntry(RelativeDirectory.forPackage(packageName)).ctSym;
186     }
187 
188     synchronized Entry getEntry(RelativeDirectory rd) throws IOException {
189         SoftReference<Entry> ref = entries.get(rd);
190         Entry e = (ref == null) ? null : ref.get();
191         if (e == null) {
192             Map<String, Path> files = new LinkedHashMap<>();
193             Set<RelativeDirectory> subdirs = new LinkedHashSet<>();
194             Path dir;
195             if (rd.path.isEmpty()) {
196                 dir = jrtfs.getPath("/modules");
197             } else {
198                 Path pkgs = jrtfs.getPath("/packages");
199                 dir = pkgs.resolve(rd.getPath().replaceAll("/$", "").replace("/", "."));
200             }
201             if (Files.exists(dir)) {
202                 try (DirectoryStream<Path> modules = Files.newDirectoryStream(dir)) {
203                     for (Path module: modules) {
204                         if (Files.isSymbolicLink(module))
205                             module = Files.readSymbolicLink(module);
206                         Path p = rd.resolveAgainst(module);
207                         if (!Files.exists(p))
208                             continue;
209                         try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
210                             for (Path entry: stream) {
211                                 String name = entry.getFileName().toString();
212                                 if (Files.isRegularFile(entry)) {
213                                     // TODO: consider issue of files with same name in different modules
214                                     files.put(name, entry);
215                                 } else if (Files.isDirectory(entry)) {
216                                     subdirs.add(new RelativeDirectory(rd, name));
217                                 }
218                             }
219                         }
220                     }
221                 }
222             }
223             e = new Entry(Collections.unmodifiableMap(files),
224                     Collections.unmodifiableSet(subdirs),
225                     getCtInfo(rd));
226             entries.put(rd, new SoftReference<>(e));
227         }
228         return e;
229     }
230 











231     public boolean isInJRT(FileObject fo) {



232         if (fo instanceof PathFileObject pathFileObject) {
233             Path path = pathFileObject.getPath();
234             return (path.getFileSystem() == jrtfs);
235         } else {
236             return false;
237         }
238     }
239 
240     private CtSym getCtInfo(RelativeDirectory dir) {
241         if (dir.path.isEmpty())
242             return CtSym.EMPTY;
243         // It's a side-effect of the default build rules that ct.properties
244         // ends up as a resource bundle.
245         if (ctBundle == null) {
246             final String bundleName = "com.sun.tools.javac.resources.ct";
247             ctBundle = ResourceBundle.getBundle(bundleName);
248         }
249         try {
250             String attrs = ctBundle.getString(dir.path.replace('/', '.') + '*');
251             boolean hidden = false;
252             boolean proprietary = false;
253             String minProfile = null;
254             for (String attr: attrs.split(" +", 0)) {
255                 switch (attr) {
256                     case "hidden":
257                         hidden = true;
258                         break;
259                     case "proprietary":
260                         proprietary = true;
261                         break;
262                     default:
263                         minProfile = attr;
264                 }
265             }
266             return new CtSym(hidden, proprietary, minProfile);
267         } catch (MissingResourceException e) {
268             return CtSym.EMPTY;
269         }
270 
271     }
272 
273     private ResourceBundle ctBundle;
274 }

  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.file;
 27 
 28 import java.io.Closeable;
 29 import java.io.IOException;
 30 import java.io.UncheckedIOException;
 31 import java.lang.ref.SoftReference;
 32 import java.net.URI;
 33 import java.nio.file.DirectoryStream;
 34 import java.nio.file.FileSystem;
 35 import java.nio.file.FileSystems;
 36 import java.nio.file.FileSystemNotFoundException;
 37 import java.nio.file.Files;
 38 import java.nio.file.Path;
 39 import java.nio.file.ProviderNotFoundException;
 40 import java.util.Collections;
 41 import java.util.HashMap;
 42 import java.util.HashSet;
 43 import java.util.LinkedHashMap;
 44 import java.util.LinkedHashSet;
 45 import java.util.Map;
 46 import java.util.MissingResourceException;
 47 import java.util.ResourceBundle;
 48 import java.util.Set;
 49 
 50 import javax.tools.FileObject;
 51 
 52 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
 53 import com.sun.tools.javac.util.Assert;
 54 
 55 /**
 56  * A package-oriented index into the jrt: filesystem.
 57  *
 58  * <p>Instances of this class may share underlying file-system resources. This
 59  * is to avoid the need for singleton instances with unbounded lifetimes which
 60  * could never release and close the underlying JRT file-system, effectively
 61  * creating a resource leak.
 62  */
 63 // Final to ensure equals/hashCode are not overridden (instance sharing relies
 64 // on default identity semantics).
 65 public final class JRTIndex implements Closeable {
 66     /**
 67      * Potentially shared access to underlying resources. Resources exist for
 68      * both preview and non-preview mode, and this field holds the version
 69      * corresponding to the preview mode flag with which it was created.
 70      */
 71     private final FileSystemResources sharedResources;
 72 
 73     /**
 74      * Create and initialize an index based on the preview mode flag.
 75      */
 76     private JRTIndex(boolean previewMode) throws IOException {
 77         this.sharedResources = FileSystemResources.claim(previewMode, this);
 78     }
 79 
 80     @Override
 81     public void close() throws IOException {
 82         // Release is atomic and succeeds at most once per index.
 83         if (!sharedResources.release(this)) {
 84             throw new IllegalStateException("JRTIndex is closed");
 85         }

 86     }
 87 
 88     /**
 89      * {@return a JRT index suitable for the given preview mode}
 90      *
 91      * <p>The returned instance must be closed by the caller.
 92      */
 93     public static JRTIndex instance(boolean previewMode) {
 94         try {
 95             return new JRTIndex(previewMode);
 96         } catch (IOException ex) {
 97             throw new UncheckedIOException(ex);



 98         }
 99     }
100 
101     /** {@return whether the JRT file-system is available to create an index} */
102     public static boolean isAvailable() {
103         try {
104             FileSystems.getFileSystem(URI.create("jrt:/"));
105             return true;
106         } catch (ProviderNotFoundException | FileSystemNotFoundException e) {
107             return false;
108         }
109     }
110 

111     /**
112      * Underlying file system resources potentially shared between many indexes.
113      *
114      * <p>This class is thread-safe so JRT indexes can be created from arbitrary
115      * threads.
116      */
117     private static class FileSystemResources {
118         // Holds the active non-preview (index 0) and preview (index 1) indexes.
119         // Active instances can be reset multiple times.
120         private static final FileSystemResources[] instances = new FileSystemResources[2];
121 
122         /** The jrt: file system. */
123         private final FileSystem jrtfs;
124 
125         /** A lazily evaluated set of entries about the contents of the jrt: file system. */
126         // Synchronized by this instance.
127         private final Map<RelativeDirectory, SoftReference<Entry>> entries = new HashMap<>();
128 
129         // The set of indexes which have claimed this resource. This assumes
130         // that index instances have default identity semantics.
131         // Synchronized by FileSystemResources.class, NOT instance.
132         private final Set<JRTIndex> owners = new HashSet<>();
133         private final boolean previewMode;
134 
135         // Created on demand in getCtInfo(), synchronized by this instance.
136         private ResourceBundle ctBundle = null;
137 
138         // Monotonic, synchronized by this instance.
139         private boolean isClosed = false;
140 
141         private FileSystemResources(boolean previewMode) throws IOException {
142             this.jrtfs = FileSystems.newFileSystem(URI.create("jrt:/"), Map.of("previewMode", Boolean.toString(previewMode)));
143             this.previewMode = previewMode;
144         }
145 
146         /** Claims shared ownership of resources for in index. */
147         static FileSystemResources claim(boolean previewMode, JRTIndex owner) throws IOException {
148             int idx = previewMode ? 1 : 0;
149             synchronized (FileSystemResources.class) {
150                 var active = instances[idx];
151                 if (active == null) {
152                     active = new FileSystemResources(previewMode);
153                     instances[idx] = active;
154                 }
155                 // Since claim is only called once per instance (during init)
156                 // seeing an index that's already claimed should be impossible.
157                 Assert.check(active.owners.add(owner));
158                 return active;
159             }
160         }
161 
162         /**
163          * Releases ownership of this resource for an index with an existing claim.
164          *
165          * @return whether the given index is being released for the first time
166          */
167         boolean release(JRTIndex owner) throws IOException {
168             int idx = previewMode ? 1 : 0;
169             boolean shouldClose;
170             synchronized (FileSystemResources.class) {
171                 Assert.check(instances[idx] == this);
172                 // Not finding a claim means the index was already released/closed.
173                 if (!owners.remove(owner)) {
174                     return false;
175                 }
176                 shouldClose = owners.isEmpty();
177                 if (shouldClose) {
178                     instances[idx] = null;
179                 }
180             }
181             if (shouldClose) {
182                 // This should be the only call to close() on the resource instance.
183                 close();
184             }
185             return true;
186         }
187 
188         /** Close underlying shared resources once no users exist (called exactly once). */
189         private synchronized void close() throws IOException {
190             Assert.check(!isClosed);
191             jrtfs.close();
192             entries.clear();
193             ctBundle = null;
194             isClosed = true;
195         }
196 
197         synchronized Entry getEntry(RelativeDirectory rd) throws IOException {
198             if (isClosed) {
199                 throw new IllegalStateException("JRTIndex is closed");
200             }
201             SoftReference<Entry> ref = entries.get(rd);
202             Entry e = (ref == null) ? null : ref.get();
203             if (e == null) {
204                 Map<String, Path> files = new LinkedHashMap<>();
205                 Set<RelativeDirectory> subdirs = new LinkedHashSet<>();
206                 Path dir;
207                 if (rd.path.isEmpty()) {
208                     dir = jrtfs.getPath("/modules");
209                 } else {
210                     Path pkgs = jrtfs.getPath("/packages");
211                     dir = pkgs.resolve(rd.getPath().replaceAll("/$", "").replace("/", "."));
212                 }
213                 if (Files.exists(dir)) {
214                     try (DirectoryStream<Path> modules = Files.newDirectoryStream(dir)) {
215                         for (Path module: modules) {
216                             if (Files.isSymbolicLink(module))
217                                 module = Files.readSymbolicLink(module);
218                             Path p = rd.resolveAgainst(module);
219                             if (!Files.exists(p))
220                                 continue;
221                             try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
222                                 for (Path entry: stream) {
223                                     String name = entry.getFileName().toString();
224                                     if (Files.isRegularFile(entry)) {
225                                         // TODO: consider issue of files with same name in different modules
226                                         files.put(name, entry);
227                                     } else if (Files.isDirectory(entry)) {
228                                         subdirs.add(new RelativeDirectory(rd, name));
229                                     }
230                                 }
231                             }
232                         }
233                     }
234                 }
235                 e = new Entry(Collections.unmodifiableMap(files),
236                         Collections.unmodifiableSet(subdirs),
237                         getCtInfo(rd));
238                 entries.put(rd, new SoftReference<>(e));
239             }
240             return e;
241         }
242 
243         private CtSym getCtInfo(RelativeDirectory dir) {
244             if (dir.path.isEmpty())
245                 return CtSym.EMPTY;
246             // It's a side-effect of the default build rules that ct.properties
247             // ends up as a resource bundle.
248             if (ctBundle == null) {
249                 final String bundleName = "com.sun.tools.javac.resources.ct";
250                 ctBundle = ResourceBundle.getBundle(bundleName);
251             }
252             try {
253                 String attrs = ctBundle.getString(dir.path.replace('/', '.') + '*');
254                 boolean hidden = false;
255                 boolean proprietary = false;
256                 String minProfile = null;
257                 for (String attr: attrs.split(" +", 0)) {
258                     switch (attr) {
259                         case "hidden":
260                             hidden = true;
261                             break;
262                         case "proprietary":
263                             proprietary = true;
264                             break;
265                         default:
266                             minProfile = attr;
267                     }
268                 }
269                 return new CtSym(hidden, proprietary, minProfile);
270             } catch (MissingResourceException e) {
271                 return CtSym.EMPTY;
272             }
273 
274         }
275 
276         boolean isJrtPath(Path p) {
277             // This still succeeds after the jrtfs is closed.
278             return (p.getFileSystem() == jrtfs);
279         }
280     }
281 
282     /**
283      * An entry provides cached info about a specific package directory within jrt:.
284      */
285     static class Entry {
286         /**
287          * The regular files for this package.
288          * For now, assume just one instance of each file across all modules.
289          */
290         final Map<String, Path> files;
291 
292         /**
293          * The set of subdirectories in jrt: for this package.
294          */
295         final Set<RelativeDirectory> subdirs;
296 
297         /**
298          * The info that used to be in ct.sym for classes in this package.
299          */
300         final CtSym ctSym;
301 
302         private Entry(Map<String, Path> files, Set<RelativeDirectory> subdirs, CtSym ctSym) {
303             this.files = files;
304             this.subdirs = subdirs;
305             this.ctSym = ctSym;

337                 sb.append("hidden");
338                 needSep = true;
339             }
340             if (proprietary) {
341                 if (needSep) sb.append(",");
342                 sb.append("proprietary");
343                 needSep = true;
344             }
345             if (minProfile != null) {
346                 if (needSep) sb.append(",");
347                 sb.append(minProfile);
348             }
349             sb.append("]");
350             return sb.toString();
351         }
352 
353         static final CtSym EMPTY = new CtSym(false, false, null);
354     }
355 
356     /**
357      * Returns a non-owned reference to the file system underlying this index.
358      *
359      * <p>When this index is closed its file system, and any {@link Path paths}
360      * derived from it, will become unusable.
361      */
362     public FileSystem getFileSystem() {
363         return sharedResources.jrtfs;

364     }
365 
366     /**
367      * Returns symbol information (possibly cached) for a given package.
368      *
369      * <p>This remains usable after the index is closed.
370      */
371     public CtSym getCtSym(CharSequence packageName) throws IOException {
372         return getEntry(RelativeDirectory.forPackage(packageName)).ctSym;
373     }
374 
375     /**
376      * Returns package information (possibly cached) for the given directory.
377      *
378      * <p>When this index is closed its file system, and any {@link Path paths}
379      * derived from it, will become unusable. This includes paths inside this
380      * entry.
381      */
382     Entry getEntry(RelativeDirectory rd) throws IOException {
383         return sharedResources.getEntry(rd);
































384     }
385 
386     /**
387      * {@returns whether the given {@link FileObject file} belongs to this index}
388      *
389      * <p>A file "belongs" to an index if it was found in that index by {@code
390      * ClassFinder}. Since indexes can differ with respect to preview mode, it
391      * is important that the {@code ClassFinder} and {@link JavacFileManager}
392      * agree on the preview mode setting being used during compilation.
393      *
394      * <p>This test will continue to succeed after the index is closed, but the
395      * file object will no longer be usable.
396      */
397     public boolean isInJRT(FileObject fo) {
398         // It is not sufficient to test if the file's path is *any* JRT path,
399         // it must exist in the file-system instance of this index (which should
400         // be the same index used by ClassFinder to obtain file objects).
401         if (fo instanceof PathFileObject pathFileObject) {
402             return sharedResources.isJrtPath(pathFileObject.getPath());

403         } else {
404             return false;
405         }
406     }



































407 }
< prev index next >