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