View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.compiler;
20  
21  import javax.lang.model.element.Modifier;
22  import javax.lang.model.element.NestingKind;
23  import javax.tools.FileObject;
24  import javax.tools.JavaFileObject;
25  import javax.tools.StandardJavaFileManager;
26  import javax.tools.StandardLocation;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.io.Reader;
33  import java.io.UncheckedIOException;
34  import java.io.Writer;
35  import java.net.URI;
36  import java.nio.charset.Charset;
37  import java.nio.file.Files;
38  import java.nio.file.Path;
39  import java.util.Arrays;
40  import java.util.Collection;
41  import java.util.HashMap;
42  import java.util.Iterator;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.Optional;
46  import java.util.Set;
47  import java.util.StringJoiner;
48  import java.util.stream.Stream;
49  import java.util.stream.StreamSupport;
50  
51  import org.apache.maven.api.JavaPathType;
52  import org.apache.maven.api.PathType;
53  
54  /**
55   * Source files for a call to {@code javac} or {@code javadoc} command to be executed as a separated process.
56   *
57   * @author Martin Desruisseaux
58   *
59   * @see ForkedCompiler
60   */
61  final class ForkedToolSources implements StandardJavaFileManager {
62      /**
63       * Option for source files. These options are not declared in
64       * {@link JavaPathType} because they are not about dependencies.
65       */
66      private record OtherPathType(String name, String optionString, String moduleName) implements PathType {
67          /**
68           * The option for the directory of source files.
69           */
70          static final OtherPathType SOURCES = new OtherPathType("SOURCES", "--source-path", null);
71  
72          /**
73           * The option for the directory of generated sources.
74           */
75          static final OtherPathType GENERATED_SOURCES = new OtherPathType("GENERATED_SOURCES", "-s", null);
76  
77          /**
78           * The option for the directory of compiled class files.
79           */
80          static final OtherPathType OUTPUT = new OtherPathType("OUTPUT", "-d", null);
81  
82          @Override
83          public String id() {
84              return name;
85          }
86  
87          @Override
88          public Optional<String> option() {
89              return Optional.of(optionString);
90          }
91  
92          /**
93           * Formats the option with the paths, <em>without quotes</em>.
94           * The quotes are omitted because the paths will be given to
95           * {@link java.lang.ProcessBuilder}, not to a command-line.
96           */
97          @Override
98          public String[] option(Iterable<? extends Path> paths) {
99              String prefix = (moduleName == null) ? "" : (moduleName + '=');
100             var builder = new StringJoiner(File.pathSeparator, prefix, "");
101             paths.forEach((path) -> builder.add(path.toString()));
102             return new String[] {optionString, builder.toString()};
103         }
104     }
105 
106     /**
107      * Search paths associated to locations.
108      * This map only stores verbatim the collections provided by callers.
109      *
110      * @see #setLocationFromPaths(Location, Collection)
111      * @see #getLocationAsPaths(Location)
112      */
113     private final Map<PathType, Collection<? extends Path>> locations;
114 
115     /**
116      * The encoding of the files to read.
117      */
118     final Charset encoding;
119 
120     /**
121      * Creates an initially empty collection of files.
122      */
123     ForkedToolSources(Charset encoding) {
124         if (encoding == null) {
125             encoding = Charset.defaultCharset();
126         }
127         this.encoding = encoding;
128         locations = new HashMap<>();
129     }
130 
131     /**
132      * Unconditionally returns -1, meaning that the given option is unsupported.
133      * Required by the interface, but not used by the Maven plugin.
134      */
135     @Override
136     public int isSupportedOption(String option) {
137         return -1;
138     }
139 
140     /**
141      * Nevers handle the given option.
142      */
143     @Override
144     public boolean handleOption(String current, Iterator<String> remaining) {
145         return false;
146     }
147 
148     /**
149      * Returns the path to the source file represented by the given object.
150      */
151     @Override
152     public Path asPath(FileObject file) {
153         return (file instanceof Item) ? ((Item) file).path : Path.of(file.toUri());
154     }
155 
156     /**
157      * Checks if the given objects represents the same canonical file.
158      * Required by the interface, but not used by the Maven plugin.
159      */
160     @Override
161     public boolean isSameFile(FileObject a, FileObject b) {
162         return asPath(a).equals(asPath(b));
163     }
164 
165     /**
166      * Returns {@code JavaFileObject} instances representing the given filenames.
167      */
168     @Override
169     public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
170         return fromNames(Arrays.stream(names));
171     }
172 
173     /**
174      * Returns {@code JavaFileObject} instances representing the given {@code File} instances.
175      */
176     @Override
177     public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
178         return fromFiles(Arrays.stream(files));
179     }
180 
181     /**
182      * Returns {@code JavaFileObject} instances representing the given filenames.
183      */
184     @Override
185     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
186         return fromNames(StreamSupport.stream(names.spliterator(), false));
187     }
188 
189     /**
190      * Returns {@code JavaFileObject} instances representing the given {@code File} instances.
191      */
192     @Override
193     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) {
194         return fromFiles(StreamSupport.stream(files.spliterator(), false));
195     }
196 
197     /**
198      * Returns {@code JavaFileObject} instances representing the given {@code Path} instances.
199      */
200     @Override
201     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(Collection<? extends Path> paths) {
202         return paths.stream().map(Item::new).toList();
203     }
204 
205     /**
206      * Helper method for the construction of {@code JavaFileObject} instances from {@code File} instances.
207      */
208     private Iterable<? extends JavaFileObject> fromFiles(Stream<? extends File> files) {
209         return files.map((file) -> new Item(file.toPath())).toList();
210     }
211 
212     /**
213      * Helper method for the construction of {@code JavaFileObject} instances from filenames.
214      */
215     private Iterable<? extends JavaFileObject> fromNames(Stream<? extends String> names) {
216         return names.map((name) -> new Item(Path.of(name))).toList();
217     }
218 
219     /**
220      * A simple implementation of Java file as a wrapper around a path. This class implements some methods
221      * as a matter of principle, but those methods should not be invoked because the file will not be opened
222      * in this Java Virtual Machine. We only need a container for a {@link Path} instance.
223      */
224     private final class Item implements JavaFileObject {
225         /**
226          * Path to the source file.
227          */
228         final Path path;
229 
230         /**
231          * Creates a new object for the given path to a Java source file.
232          */
233         Item(Path path) {
234             this.path = path;
235         }
236 
237         /**
238          * Returns the path to the source file.
239          */
240         @Override
241         public String getName() {
242             return path.toString();
243         }
244 
245         /**
246          * Returns the path to the source file.
247          */
248         @Override
249         public String toString() {
250             return getName();
251         }
252 
253         /**
254          * Returns the path as an URI.
255          */
256         @Override
257         public URI toUri() {
258             return path.toUri();
259         }
260 
261         /**
262          * Returns whether the file is a source, a class or other kind of object.
263          */
264         @Override
265         public Kind getKind() {
266             String filename = path.getFileName().toString();
267             for (Kind k : Kind.values()) {
268                 if (filename.endsWith(k.extension)) {
269                     return k;
270                 }
271             }
272             return Kind.OTHER;
273         }
274 
275         /**
276          * Returns whether this object is compatible with the given non-qualified name and the given type.
277          */
278         @Override
279         public boolean isNameCompatible(String simpleName, Kind kind) {
280             return path.getFileName().toString().equals(simpleName.concat(kind.extension));
281         }
282 
283         /**
284          * Returns {@code null}, meaning that this object as no information about nesting kind.
285          */
286         @Override
287         public NestingKind getNestingKind() {
288             return null;
289         }
290 
291         /**
292          * Returns {@code null}, meaning that this object as no information about access level.
293          */
294         @Override
295         public Modifier getAccessLevel() {
296             return null;
297         }
298 
299         /**
300          * Returns the time this file object was last modified.
301          */
302         @Override
303         public long getLastModified() {
304             try {
305                 return Files.getLastModifiedTime(path).toMillis();
306             } catch (IOException e) {
307                 throw new UncheckedIOException(e);
308             }
309         }
310 
311         /**
312          * Deletes the source file if it exists.
313          */
314         @Override
315         public boolean delete() {
316             try {
317                 return Files.deleteIfExists(path);
318             } catch (IOException e) {
319                 throw new UncheckedIOException(e);
320             }
321         }
322 
323         /**
324          * Opens the file an an input stream.
325          * Implemented as a matter of principle, but should not be invoked.
326          */
327         @Override
328         public InputStream openInputStream() throws IOException {
329             return Files.newInputStream(path);
330         }
331 
332         /**
333          * Opens the file an an output stream.
334          * Implemented as a matter of principle, but should not be invoked.
335          */
336         @Override
337         public OutputStream openOutputStream() throws IOException {
338             return Files.newOutputStream(path);
339         }
340 
341         /**
342          * Opens the file a character reader.
343          * Implemented as a matter of principle, but should not be invoked.
344          */
345         @Override
346         public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
347             return Files.newBufferedReader(path, encoding);
348         }
349 
350         /**
351          * Returns the file content.
352          * Implemented as a matter of principle, but should not be invoked.
353          */
354         @Override
355         public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
356             return Files.readString(path, encoding);
357         }
358 
359         /**
360          * Opens the file a character writer.
361          * Implemented as a matter of principle, but should not be invoked.
362          */
363         @Override
364         public Writer openWriter() throws IOException {
365             return Files.newBufferedWriter(path, encoding);
366         }
367     }
368 
369     /**
370      * Converts the {@code File} instances to {@code Path} instances and delegate.
371      * This is defined as a matter of principle but is not used by the Maven compiler plugin.
372      *
373      * @see #setLocationFromPaths(Location, Collection)
374      */
375     @Override
376     public void setLocation(Location location, Iterable<? extends File> files) {
377         List<Path> paths = null;
378         if (files != null) {
379             paths = StreamSupport.stream(files.spliterator(), false)
380                     .map(File::toPath)
381                     .toList();
382         }
383         setLocationFromPaths(location, paths);
384     }
385 
386     /**
387      * Converts the {@code Path} instances to {@code file} instances for the given location.
388      * This is defined as a matter of principle but is not used by the Maven compiler plugin.
389      *
390      * @see #setLocationFromPaths(Location, Collection)
391      */
392     @Override
393     public Iterable<? extends File> getLocation(Location location) {
394         var paths = getLocationAsPaths(location);
395         if (paths != null) {
396             return paths.stream().map(Path::toFile).toList();
397         }
398         return null;
399     }
400 
401     /**
402      * Associates the given search paths with the given location.
403      * The location may identify the class-path, module-path, doclet-path, etc.
404      * Any previous value will be discarded.
405      */
406     @Override
407     public void setLocationFromPaths(Location location, Collection<? extends Path> paths) {
408         PathType type = JavaPathType.valueOf(location).orElse(null);
409         if (type == null) {
410             if (location == StandardLocation.SOURCE_OUTPUT) {
411                 type = OtherPathType.GENERATED_SOURCES;
412             } else if (location == StandardLocation.SOURCE_PATH) {
413                 type = OtherPathType.SOURCES;
414             } else if (location == StandardLocation.CLASS_OUTPUT) {
415                 type = OtherPathType.OUTPUT;
416             } else {
417                 throw new IllegalArgumentException("Unsupported location: " + location);
418             }
419         }
420         if (paths == null || paths.isEmpty()) {
421             locations.remove(type);
422         } else {
423             locations.put(type, paths);
424         }
425     }
426 
427     /**
428      * Associates the given search paths for a module with the given location.
429      * Any previous value will be discarded.
430      */
431     @Override
432     public void setLocationForModule(Location location, String moduleName, Collection<? extends Path> paths)
433             throws IOException {
434         PathType type;
435         if (location == StandardLocation.PATCH_MODULE_PATH) {
436             type = JavaPathType.patchModule(moduleName);
437         } else if (location == StandardLocation.MODULE_SOURCE_PATH) {
438             type = new OtherPathType("MODULE_SOURCE_PATH", "--module-source-path", moduleName);
439         } else {
440             throw new IllegalArgumentException("Unsupported location: " + location);
441         }
442         if (paths == null || paths.isEmpty()) {
443             locations.remove(type);
444         } else {
445             locations.put(type, paths);
446         }
447     }
448 
449     /**
450      * Returns the search path associated with the given location, or {@code null} if none.
451      */
452     @Override
453     public Collection<? extends Path> getLocationAsPaths(Location location) {
454         return locations.get(JavaPathType.valueOf(location).orElse(null));
455     }
456 
457     /**
458      * Returns whether a location is known to this file manager.
459      * This is defined as a matter of principle but is not used by the Maven compiler plugin.
460      */
461     @Override
462     public boolean hasLocation(Location location) {
463         return getLocationAsPaths(location) != null;
464     }
465 
466     /**
467      * Adds class-path, module-path and other paths to the given command.
468      *
469      * @param command the list where to add the options
470      */
471     void addAllLocations(List<String> command) {
472         for (Map.Entry<PathType, Collection<? extends Path>> entry : locations.entrySet()) {
473             command.addAll(Arrays.asList(entry.getKey().option(entry.getValue())));
474         }
475     }
476 
477     /**
478      * Not yet implemented (not needed for forked tools).
479      */
480     @Override
481     public Iterable<JavaFileObject> list(
482             Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
483         throw new UnsupportedOperationException("Not supported yet.");
484     }
485 
486     /**
487      * Not yet implemented (not needed for forked tools).
488      */
489     @Override
490     public String inferBinaryName(Location location, JavaFileObject file) {
491         throw new UnsupportedOperationException("Not supported yet.");
492     }
493 
494     /**
495      * Not yet implemented (not needed for forked tools).
496      */
497     @Override
498     public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind)
499             throws IOException {
500         throw new UnsupportedOperationException("Not supported yet.");
501     }
502 
503     /**
504      * Not yet implemented (not needed for forked tools).
505      */
506     @Override
507     public JavaFileObject getJavaFileForOutput(
508             Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
509         throw new UnsupportedOperationException("Not supported yet.");
510     }
511 
512     /**
513      * Not yet implemented (not needed for forked tools).
514      */
515     @Override
516     public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
517         throw new UnsupportedOperationException("Not supported yet.");
518     }
519 
520     /**
521      * Not yet implemented (not needed for forked tools).
522      */
523     @Override
524     public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling)
525             throws IOException {
526         throw new UnsupportedOperationException("Not supported yet.");
527     }
528 
529     /**
530      * Returns a class loader for loading plug-ins, or {@code null} if disabled.
531      */
532     @Override
533     public ClassLoader getClassLoader(Location location) {
534         return null;
535     }
536 
537     /**
538      * Flushes any resources opened for output by this file manager.
539      */
540     @Override
541     public void flush() {}
542 
543     /**
544      * Releases any resources opened by this file manager.
545      */
546     @Override
547     public void close() {
548         locations.clear();
549     }
550 }