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.tools.FileObject;
22  import javax.tools.ForwardingJavaFileManager;
23  import javax.tools.JavaFileManager;
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.nio.file.Path;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.maven.api.JavaPathType;
40  
41  /**
42   * Workaround for a {@code javax.tools} method which seems not yet supported on all compilers.
43   * At least with OpenJDK 24, an {@link UnsupportedOperationException} may occur during the call to
44   * {@code fileManager.setLocationForModule(StandardLocation.PATCH_MODULE_PATH, moduleName, paths)}.
45   * The workaround is to format the paths in a {@code --patch-module} option instead.
46   * The problem is that we can specify this option only once per file manager instance.
47   *
48   * <p>We may remove this workaround in a future version of the Maven Compiler Plugin
49   * if the {@code UnsupportedOperationException} is fixed in a future Java release.
50   * For checking if this workaround is still necessary, set {@link #ENABLED} to {@code false}
51   * and run the JUnit tests.</p>
52   *
53   * @author Martin Desruisseaux
54   */
55  final class WorkaroundForPatchModule extends ForwardingJavaFileManager<StandardJavaFileManager>
56          implements StandardJavaFileManager {
57      /**
58       * Set this flag to {@code false} for testing if this workaround is still necessary.
59       */
60      static final boolean ENABLED = true;
61  
62      /**
63       * All locations that have been successfully specified to the file manager through programmatic API.
64       * This set excludes the {@code PATCH_MODULE_PATH} locations which were defined using the workaround
65       * described in class Javadoc.
66       */
67      private final Set<JavaFileManager.Location> definedLocations;
68  
69      /**
70       * The locations that we had to define by formatting a {@code --patch-module} option.
71       * Keys are module names and values are the paths for the associated module.
72       */
73      private final Map<String, Collection<? extends Path>> patchesAsOption;
74  
75      /**
76       * Whether the caller needs to create a new file manager.
77       * It happens when we have been unable to set a {@code --patch-module} option on the current file manager.
78       */
79      private boolean needsNewFileManager;
80  
81      /**
82       * Creates a new workaround for the given file manager.
83       */
84      WorkaroundForPatchModule(final StandardJavaFileManager fileManager) {
85          super(fileManager);
86          definedLocations = new HashSet<>();
87          patchesAsOption = new HashMap<>();
88      }
89  
90      /**
91       * {@return the original file manager, or {@code null} if the caller needs to create a new one}
92       * The returned value is {@code null} when we have been unable to set a {@code --patch-module}
93       * option on the current file manager. In such case, the caller should create a new file manager
94       * and configure it with {@link #copyTo(StandardJavaFileManager)}.
95       */
96      StandardJavaFileManager getFileManagerIfUsable() {
97          return needsNewFileManager ? null : fileManager;
98      }
99  
100     /**
101      * Copies the locations defined in this file manager to the given file manager.
102      *
103      * @param target where to copy the locations
104      * @throws IOException if a location cannot be set on the target file manager
105      */
106     void copyTo(final StandardJavaFileManager target) throws IOException {
107         for (JavaFileManager.Location location : definedLocations) {
108             target.setLocation(location, fileManager.getLocation(location));
109         }
110         for (Map.Entry<String, Collection<? extends Path>> entry : patchesAsOption.entrySet()) {
111             Collection<? extends Path> paths = entry.getValue();
112             String moduleName = entry.getKey();
113             try {
114                 target.setLocationForModule(StandardLocation.PATCH_MODULE_PATH, moduleName, paths);
115             } catch (UnsupportedOperationException e) {
116                 specifyAsOption(target, JavaPathType.patchModule(moduleName), paths, e);
117             }
118         }
119     }
120 
121     /**
122      * Sets a module path by asking the file manager to parse an option formatted by this method.
123      * Invoked when a module path cannot be specified through the API
124      * This is the workaround described in class Javadoc.
125      *
126      * @param fileManager the file manager on which an attempt to set the location has been made and failed
127      * @param type the type of path together with the module name
128      * @param paths the paths to set
129      * @param cause the exception that occurred when invoking the standard API
130      * @throws IllegalArgumentException if this workaround doesn't work neither
131      */
132     private static void specifyAsOption(
133             StandardJavaFileManager fileManager,
134             JavaPathType.Modular type,
135             Collection<? extends Path> paths,
136             UnsupportedOperationException cause)
137             throws IOException {
138 
139         String message;
140         Iterator<String> it = Arrays.asList(type.option(paths)).iterator();
141         if (!fileManager.handleOption(it.next(), it)) {
142             message = "Failed to set the %s option for module %s";
143         } else if (it.hasNext()) {
144             message = "Unexpected number of arguments after the %s option for module %s";
145         } else {
146             return;
147         }
148         JavaPathType rawType = type.rawType();
149         throw new IllegalArgumentException(
150                 String.format(message, rawType.option().orElse(rawType.name()), type.moduleName()), cause);
151     }
152 
153     /**
154      * Adds the given module path to the file manager.
155      * If we cannot do that using the programmatic API, formats as a command-line option.
156      */
157     @Override
158     public void setLocationForModule(
159             JavaFileManager.Location location, String moduleName, Collection<? extends Path> paths) throws IOException {
160 
161         if (paths.isEmpty()) {
162             return;
163         }
164         final boolean isPatch = (location == StandardLocation.PATCH_MODULE_PATH);
165         if (isPatch && patchesAsOption.replace(moduleName, paths) != null) {
166             /*
167              * The patch was already specified by formatting the `--patch-module` option.
168              * We cannot do that again, because that option can appear only once per module.
169              */
170             needsNewFileManager = true;
171             return;
172         }
173         try {
174             fileManager.setLocationForModule(location, moduleName, paths);
175         } catch (UnsupportedOperationException e) {
176             if (isPatch) {
177                 specifyAsOption(fileManager, JavaPathType.patchModule(moduleName), paths, e);
178                 patchesAsOption.put(moduleName, paths);
179                 return;
180             }
181             throw e;
182         }
183         definedLocations.add(fileManager.getLocationForModule(location, moduleName));
184     }
185 
186     /**
187      * Adds the given path to the file manager.
188      */
189     @Override
190     public void setLocationFromPaths(JavaFileManager.Location location, Collection<? extends Path> paths)
191             throws IOException {
192         fileManager.setLocationFromPaths(location, paths);
193         definedLocations.add(location);
194     }
195 
196     @Override
197     public void setLocation(Location location, Iterable<? extends File> files) throws IOException {
198         fileManager.setLocation(location, files);
199         definedLocations.add(location);
200     }
201 
202     @Override
203     public Iterable<? extends File> getLocation(Location location) {
204         return fileManager.getLocation(location);
205     }
206 
207     @Override
208     public Iterable<? extends Path> getLocationAsPaths(Location location) {
209         return fileManager.getLocationAsPaths(location);
210     }
211 
212     @Override
213     public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
214         return fileManager.getJavaFileObjects(names);
215     }
216 
217     @Override
218     public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
219         return fileManager.getJavaFileObjects(files);
220     }
221 
222     @Override
223     public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
224         return fileManager.getJavaFileObjects(paths);
225     }
226 
227     @Override
228     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
229         return fileManager.getJavaFileObjectsFromStrings(names);
230     }
231 
232     @Override
233     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) {
234         return fileManager.getJavaFileObjectsFromFiles(files);
235     }
236 
237     @Override
238     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(Collection<? extends Path> paths) {
239         return fileManager.getJavaFileObjectsFromPaths(paths);
240     }
241 
242     @Override
243     public Path asPath(FileObject file) {
244         return fileManager.asPath(file);
245     }
246 }