115 this.log = log; |
116 this.log = log; |
116 warn = lint.isEnabled(Lint.LintCategory.PATH); |
117 warn = lint.isEnabled(Lint.LintCategory.PATH); |
117 this.fsInfo = fsInfo; |
118 this.fsInfo = fsInfo; |
118 } |
119 } |
119 |
120 |
120 public Collection<File> bootClassPath() { |
121 public Collection<Path> bootClassPath() { |
121 return getLocation(PLATFORM_CLASS_PATH); |
122 return getLocation(PLATFORM_CLASS_PATH); |
122 } |
123 } |
123 |
124 |
124 public boolean isDefaultBootClassPath() { |
125 public boolean isDefaultBootClassPath() { |
125 BootClassPathLocationHandler h |
126 BootClassPathLocationHandler h |
126 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); |
127 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); |
127 return h.isDefault(); |
128 return h.isDefault(); |
128 } |
129 } |
129 |
130 |
130 public Collection<File> userClassPath() { |
131 public Collection<Path> userClassPath() { |
131 return getLocation(CLASS_PATH); |
132 return getLocation(CLASS_PATH); |
132 } |
133 } |
133 |
134 |
134 public Collection<File> sourcePath() { |
135 public Collection<Path> sourcePath() { |
135 Collection<File> p = getLocation(SOURCE_PATH); |
136 Collection<Path> p = getLocation(SOURCE_PATH); |
136 // TODO: this should be handled by the LocationHandler |
137 // TODO: this should be handled by the LocationHandler |
137 return p == null || p.isEmpty() ? null : p; |
138 return p == null || p.isEmpty() ? null : p; |
138 } |
139 } |
139 |
140 |
140 /** |
141 /** |
141 * Split a path into its elements. Empty path elements will be ignored. |
142 * Split a search path into its elements. Empty path elements will be ignored. |
142 * |
143 * |
143 * @param path The path to be split |
144 * @param searchPath The search path to be split |
144 * @return The elements of the path |
145 * @return The elements of the path |
145 */ |
146 */ |
146 private static Iterable<File> getPathEntries(String path) { |
147 private static Iterable<Path> getPathEntries(String searchPath) { |
147 return getPathEntries(path, null); |
148 return getPathEntries(searchPath, null); |
148 } |
149 } |
149 |
150 |
150 /** |
151 /** |
151 * Split a path into its elements. If emptyPathDefault is not null, all empty elements in the |
152 * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the |
152 * path, including empty elements at either end of the path, will be replaced with the value of |
153 * path, including empty elements at either end of the path, will be replaced with the value of |
153 * emptyPathDefault. |
154 * emptyPathDefault. |
154 * |
155 * |
155 * @param path The path to be split |
156 * @param searchPath The search path to be split |
156 * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore |
157 * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore |
157 * empty path elements |
158 * empty path elements |
158 * @return The elements of the path |
159 * @return The elements of the path |
159 */ |
160 */ |
160 private static Iterable<File> getPathEntries(String path, File emptyPathDefault) { |
161 private static Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) { |
161 ListBuffer<File> entries = new ListBuffer<>(); |
162 ListBuffer<Path> entries = new ListBuffer<>(); |
162 int start = 0; |
163 for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) { |
163 while (start <= path.length()) { |
164 if (s.isEmpty()) { |
164 int sep = path.indexOf(File.pathSeparatorChar, start); |
165 if (emptyPathDefault != null) { |
165 if (sep == -1) { |
166 entries.add(emptyPathDefault); |
166 sep = path.length(); |
167 } |
167 } |
168 } else { |
168 if (start < sep) { |
169 entries.add(Paths.get(s)); |
169 entries.add(new File(path.substring(start, sep))); |
170 } |
170 } else if (emptyPathDefault != null) { |
|
171 entries.add(emptyPathDefault); |
|
172 } |
|
173 start = sep + 1; |
|
174 } |
171 } |
175 return entries; |
172 return entries; |
176 } |
173 } |
177 |
174 |
178 /** |
175 /** |
179 * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths |
176 * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths |
180 * can be expanded. |
177 * can be expanded. |
181 */ |
178 */ |
182 private class SearchPath extends LinkedHashSet<File> { |
179 private class SearchPath extends LinkedHashSet<Path> { |
183 |
180 |
184 private static final long serialVersionUID = 0; |
181 private static final long serialVersionUID = 0; |
185 |
182 |
186 private boolean expandJarClassPaths = false; |
183 private boolean expandJarClassPaths = false; |
187 private final Set<File> canonicalValues = new HashSet<>(); |
184 private final Set<Path> canonicalValues = new HashSet<>(); |
188 |
185 |
189 public SearchPath expandJarClassPaths(boolean x) { |
186 public SearchPath expandJarClassPaths(boolean x) { |
190 expandJarClassPaths = x; |
187 expandJarClassPaths = x; |
191 return this; |
188 return this; |
192 } |
189 } |
193 |
190 |
194 /** |
191 /** |
195 * What to use when path element is the empty string |
192 * What to use when path element is the empty string |
196 */ |
193 */ |
197 private File emptyPathDefault = null; |
194 private Path emptyPathDefault = null; |
198 |
195 |
199 public SearchPath emptyPathDefault(File x) { |
196 public SearchPath emptyPathDefault(Path x) { |
200 emptyPathDefault = x; |
197 emptyPathDefault = x; |
201 return this; |
198 return this; |
202 } |
199 } |
203 |
200 |
204 public SearchPath addDirectories(String dirs, boolean warn) { |
201 public SearchPath addDirectories(String dirs, boolean warn) { |
205 boolean prev = expandJarClassPaths; |
202 boolean prev = expandJarClassPaths; |
206 expandJarClassPaths = true; |
203 expandJarClassPaths = true; |
207 try { |
204 try { |
208 if (dirs != null) { |
205 if (dirs != null) { |
209 for (File dir : getPathEntries(dirs)) { |
206 for (Path dir : getPathEntries(dirs)) { |
210 addDirectory(dir, warn); |
207 addDirectory(dir, warn); |
211 } |
208 } |
212 } |
209 } |
213 return this; |
210 return this; |
214 } finally { |
211 } finally { |
218 |
215 |
219 public SearchPath addDirectories(String dirs) { |
216 public SearchPath addDirectories(String dirs) { |
220 return addDirectories(dirs, warn); |
217 return addDirectories(dirs, warn); |
221 } |
218 } |
222 |
219 |
223 private void addDirectory(File dir, boolean warn) { |
220 private void addDirectory(Path dir, boolean warn) { |
224 if (!dir.isDirectory()) { |
221 if (!Files.isDirectory(dir)) { |
225 if (warn) { |
222 if (warn) { |
226 log.warning(Lint.LintCategory.PATH, |
223 log.warning(Lint.LintCategory.PATH, |
227 "dir.path.element.not.found", dir); |
224 "dir.path.element.not.found", dir); |
228 } |
225 } |
229 return; |
226 return; |
230 } |
227 } |
231 |
228 |
232 File[] files = dir.listFiles(); |
229 try (Stream<Path> s = Files.list(dir)) { |
233 if (files == null) { |
230 s.filter(dirEntry -> isArchive(dirEntry)) |
234 return; |
231 .forEach(dirEntry -> addFile(dirEntry, warn)); |
235 } |
232 } catch (IOException ignore) { |
236 |
|
237 for (File direntry : files) { |
|
238 if (isArchive(direntry)) { |
|
239 addFile(direntry, warn); |
|
240 } |
|
241 } |
233 } |
242 } |
234 } |
243 |
235 |
244 public SearchPath addFiles(String files, boolean warn) { |
236 public SearchPath addFiles(String files, boolean warn) { |
245 if (files != null) { |
237 if (files != null) { |
279 } |
271 } |
280 super.add(file); |
272 super.add(file); |
281 return; |
273 return; |
282 } |
274 } |
283 |
275 |
284 File canonFile = fsInfo.getCanonicalFile(file); |
276 Path canonFile = fsInfo.getCanonicalFile(file); |
285 if (canonicalValues.contains(canonFile)) { |
277 if (canonicalValues.contains(canonFile)) { |
286 /* Discard duplicates and avoid infinite recursion */ |
278 /* Discard duplicates and avoid infinite recursion */ |
287 return; |
279 return; |
288 } |
280 } |
289 |
281 |
290 if (fsInfo.isFile(file)) { |
282 if (fsInfo.isFile(file)) { |
291 /* File is an ordinary file. */ |
283 /* File is an ordinary file. */ |
292 if (!isArchive(file) && !file.getName().endsWith(".jimage")) { |
284 if (!isArchive(file) && !file.getFileName().toString().endsWith(".jimage")) { |
293 /* Not a recognized extension; open it to see if |
285 /* Not a recognized extension; open it to see if |
294 it looks like a valid zip file. */ |
286 it looks like a valid zip file. */ |
295 try { |
287 try { |
296 ZipFile z = new ZipFile(file); |
288 ZipFile z = new ZipFile(file.toFile()); |
297 z.close(); |
289 z.close(); |
298 if (warn) { |
290 if (warn) { |
299 log.warning(Lint.LintCategory.PATH, |
291 log.warning(Lint.LintCategory.PATH, |
300 "unexpected.archive.file", file); |
292 "unexpected.archive.file", file); |
301 } |
293 } |
313 /* Now what we have left is either a directory or a file name |
305 /* Now what we have left is either a directory or a file name |
314 conforming to archive naming convention */ |
306 conforming to archive naming convention */ |
315 super.add(file); |
307 super.add(file); |
316 canonicalValues.add(canonFile); |
308 canonicalValues.add(canonFile); |
317 |
309 |
318 if (expandJarClassPaths && fsInfo.isFile(file) && !file.getName().endsWith(".jimage")) { |
310 if (expandJarClassPaths && fsInfo.isFile(file) && !file.getFileName().toString().endsWith(".jimage")) { |
319 addJarClassPath(file, warn); |
311 addJarClassPath(file, warn); |
320 } |
312 } |
321 } |
313 } |
322 |
314 |
323 // Adds referenced classpath elements from a jar's Class-Path |
315 // Adds referenced classpath elements from a jar's Class-Path |
324 // Manifest entry. In some future release, we may want to |
316 // Manifest entry. In some future release, we may want to |
325 // update this code to recognize URLs rather than simple |
317 // update this code to recognize URLs rather than simple |
326 // filenames, but if we do, we should redo all path-related code. |
318 // filenames, but if we do, we should redo all path-related code. |
327 private void addJarClassPath(File jarFile, boolean warn) { |
319 private void addJarClassPath(Path jarFile, boolean warn) { |
328 try { |
320 try { |
329 for (File f : fsInfo.getJarClassPath(jarFile)) { |
321 for (Path f : fsInfo.getJarClassPath(jarFile)) { |
330 addFile(f, warn); |
322 addFile(f, warn); |
331 } |
323 } |
332 } catch (IOException e) { |
324 } catch (IOException e) { |
333 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); |
325 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); |
334 } |
326 } |
369 abstract boolean handleOption(Option option, String value); |
361 abstract boolean handleOption(Option option, String value); |
370 |
362 |
371 /** |
363 /** |
372 * @see StandardJavaFileManager#getLocation |
364 * @see StandardJavaFileManager#getLocation |
373 */ |
365 */ |
374 abstract Collection<File> getLocation(); |
366 abstract Collection<Path> getLocation(); |
375 |
367 |
376 /** |
368 /** |
377 * @see StandardJavaFileManager#setLocation |
369 * @see StandardJavaFileManager#setLocation |
378 */ |
370 */ |
379 abstract void setLocation(Iterable<? extends File> files) throws IOException; |
371 abstract void setLocation(Iterable<? extends Path> files) throws IOException; |
380 } |
372 } |
381 |
373 |
382 /** |
374 /** |
383 * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and |
375 * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and |
384 * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) The value is a single |
376 * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) The value is a single |
385 * file, possibly null. |
377 * file, possibly null. |
386 */ |
378 */ |
387 private class OutputLocationHandler extends LocationHandler { |
379 private class OutputLocationHandler extends LocationHandler { |
388 |
380 |
389 private File outputDir; |
381 private Path outputDir; |
390 |
382 |
391 OutputLocationHandler(Location location, Option... options) { |
383 OutputLocationHandler(Location location, Option... options) { |
392 super(location, options); |
384 super(location, options); |
393 } |
385 } |
394 |
386 |
400 |
392 |
401 // TODO: could/should validate outputDir exists and is a directory |
393 // TODO: could/should validate outputDir exists and is a directory |
402 // need to decide how best to report issue for benefit of |
394 // need to decide how best to report issue for benefit of |
403 // direct API call on JavaFileManager.handleOption(specifies IAE) |
395 // direct API call on JavaFileManager.handleOption(specifies IAE) |
404 // vs. command line decoding. |
396 // vs. command line decoding. |
405 outputDir = (value == null) ? null : new File(value); |
397 outputDir = (value == null) ? null : Paths.get(value); |
406 return true; |
398 return true; |
407 } |
399 } |
408 |
400 |
409 @Override |
401 @Override |
410 Collection<File> getLocation() { |
402 Collection<Path> getLocation() { |
411 return (outputDir == null) ? null : Collections.singleton(outputDir); |
403 return (outputDir == null) ? null : Collections.singleton(outputDir); |
412 } |
404 } |
413 |
405 |
414 @Override |
406 @Override |
415 void setLocation(Iterable<? extends File> files) throws IOException { |
407 void setLocation(Iterable<? extends Path> files) throws IOException { |
416 if (files == null) { |
408 if (files == null) { |
417 outputDir = null; |
409 outputDir = null; |
418 } else { |
410 } else { |
419 Iterator<? extends File> pathIter = files.iterator(); |
411 Iterator<? extends Path> pathIter = files.iterator(); |
420 if (!pathIter.hasNext()) { |
412 if (!pathIter.hasNext()) { |
421 throw new IllegalArgumentException("empty path for directory"); |
413 throw new IllegalArgumentException("empty path for directory"); |
422 } |
414 } |
423 File dir = pathIter.next(); |
415 Path dir = pathIter.next(); |
424 if (pathIter.hasNext()) { |
416 if (pathIter.hasNext()) { |
425 throw new IllegalArgumentException("path too long for directory"); |
417 throw new IllegalArgumentException("path too long for directory"); |
426 } |
418 } |
427 if (!dir.exists()) { |
419 if (!Files.exists(dir)) { |
428 throw new FileNotFoundException(dir + ": does not exist"); |
420 throw new FileNotFoundException(dir + ": does not exist"); |
429 } else if (!dir.isDirectory()) { |
421 } else if (!Files.isDirectory(dir)) { |
430 throw new IOException(dir + ": not a directory"); |
422 throw new IOException(dir + ": not a directory"); |
431 } |
423 } |
432 outputDir = dir; |
424 outputDir = dir; |
433 } |
425 } |
434 } |
426 } |
693 |
685 |
694 // Temporary: if no .jimage files, return individual modules |
686 // Temporary: if no .jimage files, return individual modules |
695 if (Files.exists(libModules.resolve("java.base"))) { |
687 if (Files.exists(libModules.resolve("java.base"))) { |
696 return Files.list(libModules) |
688 return Files.list(libModules) |
697 .map(d -> d.resolve("classes")) |
689 .map(d -> d.resolve("classes")) |
698 .map(Path::toFile) |
|
699 .collect(Collectors.toList()); |
690 .collect(Collectors.toList()); |
700 } |
691 } |
701 |
692 |
702 // Exploded module image |
693 // Exploded module image |
703 Path modules = Paths.get(java_home, "modules"); |
694 Path modules = Paths.get(java_home, "modules"); |
704 if (Files.isDirectory(modules.resolve("java.base"))) { |
695 if (Files.isDirectory(modules.resolve("java.base"))) { |
705 return Files.list(modules) |
696 return Files.list(modules) |
706 .map(Path::toFile) |
|
707 .collect(Collectors.toList()); |
697 .collect(Collectors.toList()); |
708 } |
698 } |
709 |
699 |
710 // not a modular image that we know about |
700 // not a modular image that we know about |
711 return null; |
701 return null; |
751 public boolean handleOption(Option option, String value) { |
741 public boolean handleOption(Option option, String value) { |
752 LocationHandler h = handlersForOption.get(option); |
742 LocationHandler h = handlersForOption.get(option); |
753 return (h == null ? false : h.handleOption(option, value)); |
743 return (h == null ? false : h.handleOption(option, value)); |
754 } |
744 } |
755 |
745 |
756 Collection<File> getLocation(Location location) { |
746 Collection<Path> getLocation(Location location) { |
757 LocationHandler h = getHandler(location); |
747 LocationHandler h = getHandler(location); |
758 return (h == null ? null : h.getLocation()); |
748 return (h == null ? null : h.getLocation()); |
759 } |
749 } |
760 |
750 |
761 File getOutputLocation(Location location) { |
751 Path getOutputLocation(Location location) { |
762 if (!location.isOutputLocation()) { |
752 if (!location.isOutputLocation()) { |
763 throw new IllegalArgumentException(); |
753 throw new IllegalArgumentException(); |
764 } |
754 } |
765 LocationHandler h = getHandler(location); |
755 LocationHandler h = getHandler(location); |
766 return ((OutputLocationHandler) h).outputDir; |
756 return ((OutputLocationHandler) h).outputDir; |
767 } |
757 } |
768 |
758 |
769 void setLocation(Location location, Iterable<? extends File> files) throws IOException { |
759 void setLocation(Location location, Iterable<? extends Path> files) throws IOException { |
770 LocationHandler h = getHandler(location); |
760 LocationHandler h = getHandler(location); |
771 if (h == null) { |
761 if (h == null) { |
772 if (location.isOutputLocation()) { |
762 if (location.isOutputLocation()) { |
773 h = new OutputLocationHandler(location); |
763 h = new OutputLocationHandler(location); |
774 } else { |
764 } else { |
785 } |
775 } |
786 |
776 |
787 /** |
777 /** |
788 * Is this the name of an archive file? |
778 * Is this the name of an archive file? |
789 */ |
779 */ |
790 private boolean isArchive(File file) { |
780 private boolean isArchive(Path file) { |
791 String n = StringUtils.toLowerCase(file.getName()); |
781 String n = StringUtils.toLowerCase(file.getFileName().toString()); |
792 return fsInfo.isFile(file) |
782 return fsInfo.isFile(file) |
793 && (n.endsWith(".jar") || n.endsWith(".zip")); |
783 && (n.endsWith(".jar") || n.endsWith(".zip")); |
794 } |
784 } |
795 |
785 |
796 /** |
786 /** |
797 * Utility method for converting a search path string to an array of directory and JAR file |
787 * Utility method for converting a search path string to an array of directory and JAR file |
798 * URLs. |
788 * URLs. |
799 * |
789 * |
800 * Note that this method is called by apt and the DocletInvoker. |
790 * Note that this method is called by the DocletInvoker. |
801 * |
791 * |
802 * @param path the search path string |
792 * @param path the search path string |
803 * @return the resulting array of directory and JAR file URLs |
793 * @return the resulting array of directory and JAR file URLs |
804 */ |
794 */ |
805 public static URL[] pathToURLs(String path) { |
795 public static URL[] pathToURLs(String path) { |
806 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); |
796 java.util.List<URL> urls = new ArrayList<>(); |
807 URL[] urls = new URL[st.countTokens()]; |
797 for (String s: path.split(Pattern.quote(File.pathSeparator))) { |
808 int count = 0; |
798 if (!s.isEmpty()) { |
809 while (st.hasMoreTokens()) { |
799 URL url = fileToURL(Paths.get(s)); |
810 URL url = fileToURL(new File(st.nextToken())); |
800 if (url != null) { |
811 if (url != null) { |
801 urls.add(url); |
812 urls[count++] = url; |
802 } |
813 } |
803 } |
814 } |
804 } |
815 urls = Arrays.copyOf(urls, count); |
805 return urls.toArray(new URL[urls.size()]); |
816 return urls; |
|
817 } |
806 } |
818 |
807 |
819 /** |
808 /** |
820 * Returns the directory or JAR file URL corresponding to the specified local file name. |
809 * Returns the directory or JAR file URL corresponding to the specified local file name. |
821 * |
810 * |
822 * @param file the File object |
811 * @param file the Path object |
823 * @return the resulting directory or JAR file URL, or null if unknown |
812 * @return the resulting directory or JAR file URL, or null if unknown |
824 */ |
813 */ |
825 private static URL fileToURL(File file) { |
814 private static URL fileToURL(Path file) { |
826 String name; |
815 Path p; |
827 try { |
816 try { |
828 name = file.getCanonicalPath(); |
817 p = file.toRealPath(); |
829 } catch (IOException e) { |
818 } catch (IOException e) { |
830 name = file.getAbsolutePath(); |
819 p = file.toAbsolutePath(); |
831 } |
|
832 name = name.replace(File.separatorChar, '/'); |
|
833 if (!name.startsWith("/")) { |
|
834 name = "/" + name; |
|
835 } |
|
836 // If the file does not exist, then assume that it's a directory |
|
837 if (!file.isFile()) { |
|
838 name = name + "/"; |
|
839 } |
820 } |
840 try { |
821 try { |
841 return new URL("file", "", name); |
822 return p.normalize().toUri().toURL(); |
842 } catch (MalformedURLException e) { |
823 } catch (MalformedURLException e) { |
843 throw new IllegalArgumentException(file.toString()); |
824 return null; |
844 } |
825 } |
845 } |
826 } |
846 } |
827 } |