31 import java.io.OutputStream; |
31 import java.io.OutputStream; |
32 import java.io.OutputStreamWriter; |
32 import java.io.OutputStreamWriter; |
33 import java.io.Reader; |
33 import java.io.Reader; |
34 import java.io.Writer; |
34 import java.io.Writer; |
35 import java.net.URI; |
35 import java.net.URI; |
|
36 import java.net.URISyntaxException; |
36 import java.nio.ByteBuffer; |
37 import java.nio.ByteBuffer; |
37 import java.nio.CharBuffer; |
38 import java.nio.CharBuffer; |
38 import java.nio.charset.CharsetDecoder; |
39 import java.nio.charset.CharsetDecoder; |
|
40 import java.nio.file.FileSystem; |
|
41 import java.nio.file.FileSystems; |
39 import java.nio.file.Files; |
42 import java.nio.file.Files; |
40 import java.nio.file.LinkOption; |
43 import java.nio.file.LinkOption; |
41 import java.nio.file.Path; |
44 import java.nio.file.Path; |
|
45 import java.text.Normalizer; |
42 import java.util.Objects; |
46 import java.util.Objects; |
43 |
47 |
44 import javax.lang.model.element.Modifier; |
48 import javax.lang.model.element.Modifier; |
45 import javax.lang.model.element.NestingKind; |
49 import javax.lang.model.element.NestingKind; |
|
50 import javax.tools.FileObject; |
46 import javax.tools.JavaFileObject; |
51 import javax.tools.JavaFileObject; |
47 |
52 |
|
53 import com.sun.tools.javac.file.RelativePath.RelativeFile; |
48 import com.sun.tools.javac.util.DefinedBy; |
54 import com.sun.tools.javac.util.DefinedBy; |
49 import com.sun.tools.javac.util.DefinedBy.Api; |
55 import com.sun.tools.javac.util.DefinedBy.Api; |
50 |
56 |
51 |
57 |
52 /** |
58 /** |
53 * Implementation of JavaFileObject using java.nio.file API. |
59 * Implementation of JavaFileObject using java.nio.file API. |
54 * |
60 * |
55 * <p>PathFileObjects are, for the most part, straightforward wrappers around |
61 * <p>PathFileObjects are, for the most part, straightforward wrappers around |
56 * Path objects. The primary complexity is the support for "inferBinaryName". |
62 * immutable absolute Path objects. Different subtypes are used to provide |
57 * This is left as an abstract method, implemented by each of a number of |
63 * specialized implementations of "inferBinaryName" and "getName" that capture |
58 * different factory methods, which compute the binary name based on |
64 * additional information available at the time the object is created. |
59 * information available at the time the file object is created. |
65 * |
|
66 * <p>In general, {@link JavaFileManager#isSameFile} should be used to |
|
67 * determine whether two file objects refer to the same file on disk. |
|
68 * PathFileObject also supports the standard {@code equals} and {@code hashCode} |
|
69 * methods, primarily for convenience when working with collections. |
|
70 * All of these operations delegate to the equivalent operations on the |
|
71 * underlying Path object. |
60 * |
72 * |
61 * <p><b>This is NOT part of any supported API. |
73 * <p><b>This is NOT part of any supported API. |
62 * If you write code that depends on this, you do so at your own risk. |
74 * If you write code that depends on this, you do so at your own risk. |
63 * This code and its internal interfaces are subject to change or |
75 * This code and its internal interfaces are subject to change or |
64 * deletion without notice.</b> |
76 * deletion without notice.</b> |
65 */ |
77 */ |
66 public abstract class PathFileObject implements JavaFileObject { |
78 public abstract class PathFileObject implements JavaFileObject { |
67 private final BaseFileManager fileManager; |
79 private static final FileSystem defaultFileSystem = FileSystems.getDefault(); |
68 private final Path path; |
80 private static final boolean isMacOS = System.getProperty("os.name", "").contains("OS X"); |
69 |
81 |
70 /** |
82 protected final BaseFileManager fileManager; |
71 * Create a PathFileObject within a directory, such that the binary name |
83 protected final Path path; |
72 * can be inferred from the relationship to the parent directory. |
84 private boolean hasParents; |
73 */ |
85 |
74 static PathFileObject createDirectoryPathFileObject(BaseFileManager fileManager, |
86 /** |
75 final Path path, final Path dir) { |
87 * Create a PathFileObject for a file within a directory, such that the |
76 return new PathFileObject(fileManager, path) { |
88 * binary name can be inferred from the relationship to an enclosing directory. |
77 @Override |
89 * |
78 public String inferBinaryName(Iterable<? extends Path> paths) { |
90 * The binary name is derived from {@code relativePath}. |
79 return toBinaryName(dir.relativize(path)); |
91 * The name is derived from the composition of {@code userPackageRootDir} |
|
92 * and {@code relativePath}. |
|
93 * |
|
94 * @param fileManager the file manager creating this file object |
|
95 * @param path the absolute path referred to by this file object |
|
96 * @param userPackageRootDir the path of the directory containing the |
|
97 * root of the package hierarchy |
|
98 * @param relativePath the path of this file relative to {@code userPackageRootDir} |
|
99 */ |
|
100 static PathFileObject forDirectoryPath(BaseFileManager fileManager, Path path, |
|
101 Path userPackageRootDir, RelativePath relativePath) { |
|
102 return new DirectoryFileObject(fileManager, path, userPackageRootDir, relativePath); |
|
103 } |
|
104 |
|
105 private static class DirectoryFileObject extends PathFileObject { |
|
106 private final Path userPackageRootDir; |
|
107 private final RelativePath relativePath; |
|
108 |
|
109 private DirectoryFileObject(BaseFileManager fileManager, Path path, |
|
110 Path userPackageRootDir, RelativePath relativePath) { |
|
111 super(fileManager, path); |
|
112 this.userPackageRootDir = userPackageRootDir; |
|
113 this.relativePath = relativePath; |
|
114 } |
|
115 |
|
116 @Override @DefinedBy(Api.COMPILER) |
|
117 public String getName() { |
|
118 return relativePath.resolveAgainst(userPackageRootDir).toString(); |
|
119 } |
|
120 |
|
121 @Override |
|
122 public String inferBinaryName(Iterable<? extends Path> paths) { |
|
123 return toBinaryName(relativePath); |
|
124 } |
|
125 |
|
126 @Override |
|
127 public String toString() { |
|
128 return "DirectoryFileObject[" + userPackageRootDir + ":" + relativePath.path + "]"; |
|
129 } |
|
130 |
|
131 @Override |
|
132 PathFileObject getSibling(String baseName) { |
|
133 return new DirectoryFileObject(fileManager, |
|
134 path.resolveSibling(baseName), |
|
135 userPackageRootDir, |
|
136 new RelativeFile(relativePath.dirname(), baseName) |
|
137 ); |
|
138 } |
|
139 } |
|
140 |
|
141 /** |
|
142 * Create a PathFileObject for a file in a file system such as a jar file, |
|
143 * such that the binary name can be inferred from its position within the |
|
144 * file system. |
|
145 * |
|
146 * The binary name is derived from {@code path}. |
|
147 * The name is derived from the composition of {@code userJarPath} |
|
148 * and {@code path}. |
|
149 * |
|
150 * @param fileManager the file manager creating this file object |
|
151 * @param path the path referred to by this file object |
|
152 * @param userJarPath the path of the jar file containing the file system. |
|
153 */ |
|
154 public static PathFileObject forJarPath(BaseFileManager fileManager, |
|
155 Path path, Path userJarPath) { |
|
156 return new JarFileObject(fileManager, path, userJarPath); |
|
157 } |
|
158 |
|
159 private static class JarFileObject extends PathFileObject { |
|
160 private final Path userJarPath; |
|
161 |
|
162 private JarFileObject(BaseFileManager fileManager, Path path, Path userJarPath) { |
|
163 super(fileManager, path); |
|
164 this.userJarPath = userJarPath; |
|
165 } |
|
166 |
|
167 @Override @DefinedBy(Api.COMPILER) |
|
168 public String getName() { |
|
169 // The use of ( ) to delimit the entry name is not ideal |
|
170 // but it does match earlier behavior |
|
171 return userJarPath + "(" + path + ")"; |
|
172 } |
|
173 |
|
174 @Override |
|
175 public String inferBinaryName(Iterable<? extends Path> paths) { |
|
176 Path root = path.getFileSystem().getRootDirectories().iterator().next(); |
|
177 return toBinaryName(root.relativize(path)); |
|
178 } |
|
179 |
|
180 @Override @DefinedBy(Api.COMPILER) |
|
181 public URI toUri() { |
|
182 // Work around bug JDK-8134451: |
|
183 // path.toUri() returns double-encoded URIs, that cannot be opened by URLConnection |
|
184 return createJarUri(userJarPath, path.toString()); |
|
185 } |
|
186 |
|
187 @Override |
|
188 public String toString() { |
|
189 return "JarFileObject[" + userJarPath + ":" + path + "]"; |
|
190 } |
|
191 |
|
192 @Override |
|
193 PathFileObject getSibling(String baseName) { |
|
194 return new JarFileObject(fileManager, |
|
195 path.resolveSibling(baseName), |
|
196 userJarPath |
|
197 ); |
|
198 } |
|
199 |
|
200 private static URI createJarUri(Path jarFile, String entryName) { |
|
201 URI jarURI = jarFile.toUri().normalize(); |
|
202 String separator = entryName.startsWith("/") ? "!" : "!/"; |
|
203 try { |
|
204 // The jar URI convention appears to be not to re-encode the jarURI |
|
205 return new URI("jar:" + jarURI + separator + entryName); |
|
206 } catch (URISyntaxException e) { |
|
207 throw new CannotCreateUriError(jarURI + separator + entryName, e); |
80 } |
208 } |
81 }; |
209 } |
82 } |
210 } |
83 |
211 |
84 /** |
212 /** |
85 * Create a PathFileObject in a file system such as a jar file, such that |
213 * Create a PathFileObject for a file in a modular file system, such as jrt:, |
86 * the binary name can be inferred from its position within the filesystem. |
214 * such that the binary name can be inferred from its position within the |
87 */ |
215 * filesystem. |
88 public static PathFileObject createJarPathFileObject(BaseFileManager fileManager, |
216 * |
|
217 * The binary name is derived from {@code path}, ignoring the first two |
|
218 * elements of the name (which are "modules" and a module name). |
|
219 * The name is derived from {@code path}. |
|
220 * |
|
221 * @param fileManager the file manager creating this file object |
|
222 * @param path the path referred to by this file object |
|
223 */ |
|
224 public static PathFileObject forJRTPath(BaseFileManager fileManager, |
89 final Path path) { |
225 final Path path) { |
90 return new PathFileObject(fileManager, path) { |
226 return new JRTFileObject(fileManager, path); |
91 @Override |
227 } |
92 public String inferBinaryName(Iterable<? extends Path> paths) { |
228 |
93 return toBinaryName(path); |
229 private static class JRTFileObject extends PathFileObject { |
94 } |
230 // private final Path javaHome; |
95 }; |
231 private JRTFileObject(BaseFileManager fileManager, Path path) { |
96 } |
232 super(fileManager, path); |
97 |
233 } |
98 /** |
234 |
99 * Create a PathFileObject in a modular file system, such as jrt:, such that |
235 @Override @DefinedBy(Api.COMPILER) |
100 * the binary name can be inferred from its position within the filesystem. |
236 public String getName() { |
101 */ |
237 return path.toString(); |
102 public static PathFileObject createJRTPathFileObject(BaseFileManager fileManager, |
238 } |
103 final Path path) { |
239 |
104 return new PathFileObject(fileManager, path) { |
240 @Override |
105 @Override |
241 public String inferBinaryName(Iterable<? extends Path> paths) { |
106 public String inferBinaryName(Iterable<? extends Path> paths) { |
242 // use subpath to ignore the leading /modules/MODULE-NAME |
107 // use subpath to ignore the leading /modules/MODULE-NAME |
243 return toBinaryName(path.subpath(2, path.getNameCount())); |
108 return toBinaryName(path.subpath(2, path.getNameCount())); |
244 } |
109 } |
245 |
110 }; |
246 @Override |
111 } |
247 public String toString() { |
112 |
248 return "JRTFileObject[" + path + "]"; |
113 /** |
249 } |
114 * Create a PathFileObject whose binary name can be inferred from the |
250 |
115 * relative path to a sibling. |
251 @Override |
116 */ |
252 PathFileObject getSibling(String baseName) { |
117 static PathFileObject createSiblingPathFileObject(BaseFileManager fileManager, |
253 return new JRTFileObject(fileManager, |
118 final Path path, final String relativePath) { |
254 path.resolveSibling(baseName) |
119 return new PathFileObject(fileManager, path) { |
255 ); |
120 @Override |
256 } |
121 public String inferBinaryName(Iterable<? extends Path> paths) { |
257 } |
122 return toBinaryName(relativePath, "/"); |
258 |
123 } |
259 /** |
124 }; |
260 * Create a PathFileObject for a file whose binary name must be inferred |
125 } |
261 * from its position on a search path. |
126 |
262 * |
127 /** |
263 * The binary name is inferred by finding an enclosing directory in |
128 * Create a PathFileObject whose binary name might be inferred from its |
264 * the sequence of paths associated with the location given to |
129 * position on a search path. |
265 * {@link JavaFileManager#inferBinaryName). |
130 */ |
266 * The name is derived from {@code userPath}. |
131 static PathFileObject createSimplePathFileObject(BaseFileManager fileManager, |
267 * |
132 final Path path) { |
268 * @param fileManager the file manager creating this file object |
133 return new PathFileObject(fileManager, path) { |
269 * @param path the path referred to by this file object |
134 @Override |
270 * @param userPath the "user-friendly" name for this path. |
135 public String inferBinaryName(Iterable<? extends Path> paths) { |
271 */ |
136 Path absPath = path.toAbsolutePath(); |
272 static PathFileObject forSimplePath(BaseFileManager fileManager, |
137 for (Path p: paths) { |
273 Path path, Path userPath) { |
138 Path ap = p.toAbsolutePath(); |
274 return new SimpleFileObject(fileManager, path, userPath); |
139 if (absPath.startsWith(ap)) { |
275 } |
140 try { |
276 |
141 Path rp = ap.relativize(absPath); |
277 private static class SimpleFileObject extends PathFileObject { |
142 if (rp != null) // maybe null if absPath same as ap |
278 private final Path userPath; |
143 return toBinaryName(rp); |
279 private SimpleFileObject(BaseFileManager fileManager, Path path, Path userPath) { |
144 } catch (IllegalArgumentException e) { |
280 super(fileManager, path); |
145 // ignore this p if cannot relativize path to p |
281 this.userPath = userPath; |
146 } |
282 } |
|
283 |
|
284 @Override @DefinedBy(Api.COMPILER) |
|
285 public String getName() { |
|
286 return userPath.toString(); |
|
287 } |
|
288 |
|
289 @Override |
|
290 public String inferBinaryName(Iterable<? extends Path> paths) { |
|
291 Path absPath = path.toAbsolutePath(); |
|
292 for (Path p: paths) { |
|
293 Path ap = p.toAbsolutePath(); |
|
294 if (absPath.startsWith(ap)) { |
|
295 try { |
|
296 Path rp = ap.relativize(absPath); |
|
297 if (rp != null) // maybe null if absPath same as ap |
|
298 return toBinaryName(rp); |
|
299 } catch (IllegalArgumentException e) { |
|
300 // ignore this p if cannot relativize path to p |
147 } |
301 } |
148 } |
302 } |
149 return null; |
|
150 } |
303 } |
151 }; |
304 return null; |
152 } |
305 } |
153 |
306 |
|
307 @Override |
|
308 PathFileObject getSibling(String baseName) { |
|
309 return new SimpleFileObject(fileManager, |
|
310 path.resolveSibling(baseName), |
|
311 userPath.resolveSibling(baseName) |
|
312 ); |
|
313 } |
|
314 } |
|
315 |
|
316 /** |
|
317 * Create a PathFileObject, for a specified path, in the context of |
|
318 * a given file manager. |
|
319 * |
|
320 * In general, this path should be an |
|
321 * {@link Path#toAbsolutePath absolute path}, if not a |
|
322 * {@link Path#toRealPath} real path. |
|
323 * It will be used as the basis of {@code equals}, {@code hashCode} |
|
324 * and {@code isSameFile} methods on this file object. |
|
325 * |
|
326 * A PathFileObject should also have a "friendly name" per the |
|
327 * specification for {@link FileObject#getName}. The friendly name |
|
328 * is provided by the various subtypes of {@code PathFileObject}. |
|
329 * |
|
330 * @param fileManager the file manager creating this file object |
|
331 * @param path the path contained in this file object. |
|
332 */ |
154 protected PathFileObject(BaseFileManager fileManager, Path path) { |
333 protected PathFileObject(BaseFileManager fileManager, Path path) { |
155 this.fileManager = Objects.requireNonNull(fileManager); |
334 this.fileManager = Objects.requireNonNull(fileManager); |
156 this.path = Objects.requireNonNull(path); |
335 if (Files.isDirectory(path)) { |
157 } |
336 throw new IllegalArgumentException("directories not supported"); |
158 |
337 } |
159 public abstract String inferBinaryName(Iterable<? extends Path> paths); |
338 this.path = path; |
|
339 } |
|
340 |
|
341 /** |
|
342 * See {@link JavacFileManager#inferBinaryName}. |
|
343 */ |
|
344 abstract String inferBinaryName(Iterable<? extends Path> paths); |
|
345 |
|
346 /** |
|
347 * Return the file object for a sibling file with a given file name. |
|
348 * See {@link JavacFileManager#getFileForOutput} and |
|
349 * {@link JavacFileManager#getJavaFileForOutput}. |
|
350 */ |
|
351 abstract PathFileObject getSibling(String basename); |
160 |
352 |
161 /** |
353 /** |
162 * Return the Path for this object. |
354 * Return the Path for this object. |
163 * @return the Path for this object. |
355 * @return the Path for this object. |
|
356 * @see StandardJavaFileManager#asPath |
164 */ |
357 */ |
165 public Path getPath() { |
358 public Path getPath() { |
166 return path; |
359 return path; |
167 } |
360 } |
168 |
361 |
|
362 /** |
|
363 * The short name is used when generating raw diagnostics. |
|
364 * @return the last component of the path |
|
365 */ |
|
366 public String getShortName() { |
|
367 return path.getFileName().toString(); |
|
368 } |
|
369 |
169 @Override @DefinedBy(Api.COMPILER) |
370 @Override @DefinedBy(Api.COMPILER) |
170 public Kind getKind() { |
371 public Kind getKind() { |
171 return BaseFileManager.getKind(path.getFileName().toString()); |
372 return BaseFileManager.getKind(path.getFileName().toString()); |
172 } |
373 } |
173 |
374 |
174 @Override @DefinedBy(Api.COMPILER) |
375 @Override @DefinedBy(Api.COMPILER) |
175 public boolean isNameCompatible(String simpleName, Kind kind) { |
376 public boolean isNameCompatible(String simpleName, Kind kind) { |
176 Objects.requireNonNull(simpleName); |
377 Objects.requireNonNull(simpleName); |
177 // null check |
378 Objects.requireNonNull(kind); |
|
379 |
178 if (kind == Kind.OTHER && getKind() != kind) { |
380 if (kind == Kind.OTHER && getKind() != kind) { |
179 return false; |
381 return false; |
180 } |
382 } |
|
383 |
181 String sn = simpleName + kind.extension; |
384 String sn = simpleName + kind.extension; |
182 String pn = path.getFileName().toString(); |
385 String pn = path.getFileName().toString(); |
183 if (pn.equals(sn)) { |
386 if (pn.equals(sn)) { |
184 return true; |
387 return true; |
185 } |
388 } |
186 if (pn.equalsIgnoreCase(sn)) { |
389 |
187 try { |
390 if (path.getFileSystem() == defaultFileSystem) { |
188 // allow for Windows |
391 if (isMacOS) { |
189 return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn); |
392 String name = path.getFileName().toString(); |
190 } catch (IOException e) { |
393 if (Normalizer.isNormalized(name, Normalizer.Form.NFD) |
|
394 && Normalizer.isNormalized(sn, Normalizer.Form.NFC)) { |
|
395 // On Mac OS X it is quite possible to have the file name and the |
|
396 // given simple name normalized in different ways. |
|
397 // In that case we have to normalize file name to the |
|
398 // Normal Form Composed (NFC). |
|
399 String normName = Normalizer.normalize(name, Normalizer.Form.NFC); |
|
400 if (normName.equals(sn)) { |
|
401 return true; |
|
402 } |
|
403 } |
191 } |
404 } |
192 } |
405 |
|
406 if (pn.equalsIgnoreCase(sn)) { |
|
407 try { |
|
408 // allow for Windows |
|
409 return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn); |
|
410 } catch (IOException e) { |
|
411 } |
|
412 } |
|
413 } |
|
414 |
193 return false; |
415 return false; |
194 } |
416 } |
195 |
417 |
196 @Override @DefinedBy(Api.COMPILER) |
418 @Override @DefinedBy(Api.COMPILER) |
197 public NestingKind getNestingKind() { |
419 public NestingKind getNestingKind() { |