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