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 }
|