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.stubs;
20  
21  import javax.annotation.processing.Processor;
22  import javax.lang.model.SourceVersion;
23  import javax.tools.DiagnosticListener;
24  import javax.tools.FileObject;
25  import javax.tools.JavaCompiler;
26  import javax.tools.JavaFileManager;
27  import javax.tools.JavaFileObject;
28  import javax.tools.SimpleJavaFileObject;
29  import javax.tools.StandardJavaFileManager;
30  import javax.tools.StandardLocation;
31  
32  import java.io.File;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.OutputStream;
36  import java.io.UncheckedIOException;
37  import java.io.Writer;
38  import java.nio.charset.Charset;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Locale;
44  import java.util.Set;
45  
46  /**
47   * A dummy implementation of the {@code JavaCompiler} interface for testing the Maven compiler plugin
48   * with alternative compilers. This dummy compiler actually ignores all source files and always writes
49   * exactly one output file, namely {@value #OUTPUT_FILE}.
50   *
51   * <h2>Instantiation</h2>
52   * This stub is not instantiated directly. Instead, the fully-qualified class name must be declared
53   * in the {@code META-INF/services/javax.tools.Tool} file. Then, an instance is requested by setting
54   * the {@code <compilerId>} element to {@value #COMPILER_ID} in the {@code plugin-config.xml} file
55   * of the test.
56   *
57   * @author Edwin Punzalan
58   * @author Martin Desruisseaux
59   */
60  public class CompilerStub implements JavaCompiler, StandardJavaFileManager {
61      /**
62       * The name returned by {@link #name()}. Used for identifying this stub.
63       * This is the value to specify in the {@code <compilerId>} element of the POM test file.
64       *
65       * @see #name()
66       */
67      public static final String COMPILER_ID = "maven-compiler-stub";
68  
69      /**
70       * Name of the dummy file created as output by this compiler stub.
71       *
72       * @see #inferBinaryName(JavaFileManager.Location, JavaFileObject)
73       */
74      public static final String OUTPUT_FILE = "compiled.class";
75  
76      /**
77       * The output directory, or {@code null} if not yet set.
78       *
79       * @see #setLocation(JavaFileManager.Location, Iterable)
80       */
81      private File outputDir;
82  
83      /**
84       * Options given to the compiler when executed.
85       *
86       * @see #getOptions()
87       */
88      private static final ThreadLocal<Iterable<String>> ARGUMENTS = new ThreadLocal<>();
89  
90      /**
91       * Invoked by reflection by {@link java.util.ServiceLoader}.
92       */
93      public CompilerStub() {}
94  
95      /**
96       * {@return the compiler idenitifer of this stub}
97       */
98      @Override
99      public String name() {
100         return COMPILER_ID;
101     }
102 
103     /**
104      * {@return an arbitrary Java release number}
105      * This is not used by the tests.
106      */
107     @Override
108     public Set<SourceVersion> getSourceVersions() {
109         return Set.of(SourceVersion.RELEASE_17);
110     }
111 
112     /**
113      * {@return the number of arguments expected by the given option}
114      * This method is implemented by a hard-coded list of options that
115      * are known to be used in some tests.
116      */
117     @Override
118     public int isSupportedOption(String option) {
119         if (option.startsWith("-my&")) {
120             return 0;
121         }
122         switch (option) {
123             case "-Xlint":
124                 return 0;
125             default:
126                 return 1;
127         }
128     }
129 
130     /**
131      * {@return the object where source and destination directories will be specified by the Maven compiler plugin}
132      */
133     @Override
134     public StandardJavaFileManager getStandardFileManager(
135             DiagnosticListener<? super JavaFileObject> diagnosticListener, Locale locale, Charset charset) {
136         return this;
137     }
138 
139     /**
140      * {@return whether the two given objects are for the same file}
141      * This method is not seriously implemented, as it is not needed for the tests.
142      */
143     @Override
144     public boolean isSameFile(FileObject a, FileObject b) {
145         return a.equals(b);
146     }
147 
148     /**
149      * Source or target directory, or source file to compile.
150      * For this test, we do not bother to identify the exact purpose of the wrapped file.
151      */
152     private static final class UnknownFile extends SimpleJavaFileObject {
153         UnknownFile(final File file) {
154             super(file.toURI(), JavaFileObject.Kind.OTHER);
155         }
156 
157         UnknownFile(final String name) {
158             this(new File(name));
159         }
160     }
161 
162     /**
163      * {@return the given files or directories wrapped in a dummy implementation of {@code FileObject}}
164      */
165     @Override
166     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) {
167         var objects = new ArrayList<JavaFileObject>();
168         files.forEach(UnknownFile::new);
169         return objects;
170     }
171 
172     /**
173      * {@return the given files or directories wrapped in a dummy implementation of {@code FileObject}}
174      */
175     @Override
176     public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
177         return getJavaFileObjectsFromFiles(Arrays.asList(files));
178     }
179 
180     /**
181      * {@return the given files or directories wrapped in a dummy implementation of {@code FileObject}}
182      */
183     @Override
184     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
185         var objects = new ArrayList<JavaFileObject>();
186         names.forEach(UnknownFile::new);
187         return objects;
188     }
189 
190     /**
191      * {@return the given files or directories wrapped in a dummy implementation of {@code FileObject}}
192      */
193     @Override
194     public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
195         return getJavaFileObjectsFromStrings(Arrays.asList(names));
196     }
197 
198     /**
199      * {@return whether the given location is known to this file manager}
200      */
201     @Override
202     public boolean hasLocation(Location location) {
203         return location == StandardLocation.CLASS_OUTPUT;
204     }
205 
206     /**
207      * Sets a directory for the given type of location. This simple stubs accepts a single
208      * directory for {@link StandardLocation#CLASS_OUTPUT} and ignores all other locations.
209      */
210     @Override
211     public void setLocation(Location location, Iterable<? extends File> files) {
212         if (location == StandardLocation.CLASS_OUTPUT) {
213             outputDir = null;
214             Iterator<? extends File> it = files.iterator();
215             if (it.hasNext()) {
216                 outputDir = it.next();
217                 if (it.hasNext()) {
218                     throw new IllegalArgumentException("This simple stub accepts a maximum of one output directory.");
219                 }
220             }
221         }
222     }
223 
224     /**
225      * {@return the directory for the given type of location}
226      */
227     @Override
228     public Iterable<? extends File> getLocation(Location location) {
229         if (location == StandardLocation.CLASS_OUTPUT && outputDir != null) {
230             return Set.of(outputDir);
231         }
232         return Set.of();
233     }
234 
235     /**
236      * Not used by the tests.
237      */
238     @Override
239     public ClassLoader getClassLoader(Location location) {
240         return Thread.currentThread().getContextClassLoader();
241     }
242 
243     /**
244      * Not used by the tests.
245      */
246     @Override
247     public Iterable<JavaFileObject> list(
248             Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) {
249         return Set.of();
250     }
251 
252     /**
253      * {@returns the name of the single file created by this dummy compiler}.
254      */
255     @Override
256     public String inferBinaryName(Location location, JavaFileObject file) {
257         return OUTPUT_FILE;
258     }
259 
260     /**
261      * Not used by the tests.
262      */
263     @Override
264     public boolean handleOption(String current, Iterator<String> remaining) {
265         return false;
266     }
267 
268     /**
269      * Not used by the tests.
270      */
271     @Override
272     public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) {
273         return null;
274     }
275 
276     /**
277      * Not used by the tests.
278      */
279     @Override
280     public JavaFileObject getJavaFileForOutput(
281             Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
282         return null;
283     }
284 
285     /**
286      * Not used by the tests.
287      */
288     @Override
289     public FileObject getFileForInput(Location location, String packageName, String relativeName) {
290         return null;
291     }
292 
293     /**
294      * Not used by the tests.
295      */
296     @Override
297     public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) {
298         return null;
299     }
300 
301     /**
302      * {@return a compilation task}
303      */
304     @Override
305     public CompilationTask getTask(
306             Writer out,
307             JavaFileManager fileManager,
308             DiagnosticListener<? super JavaFileObject> diagnosticListener,
309             Iterable<String> options,
310             Iterable<String> classes,
311             Iterable<? extends JavaFileObject> compilationUnits) {
312 
313         ARGUMENTS.set(options);
314         return new CompilationTask() {
315             @Override
316             public void addModules(Iterable<String> moduleNames) {}
317 
318             @Override
319             public void setProcessors(Iterable<? extends Processor> processors) {}
320 
321             @Override
322             public void setLocale(Locale locale) {}
323 
324             /**
325              * Executes the pseudo-compilation.
326              *
327              * @return true for success, false otherwise
328              */
329             @Override
330             public Boolean call() {
331                 return run(null, null, null, (String[]) null) == 0;
332             }
333         };
334     }
335 
336     /**
337      * Executes the pseudo-compilation.
338      *
339      * @return 0 for success, nonzero otherwise
340      */
341     @Override
342     public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) {
343         try {
344             outputDir.mkdirs();
345             File outputFile = new File(outputDir, OUTPUT_FILE);
346             outputFile.createNewFile();
347         } catch (IOException e) {
348             throw new UncheckedIOException("An exception occurred while creating output file.", e);
349         }
350         return 0;
351     }
352 
353     /**
354      * {@return the options given to the compiler when the compilation tasks was created}
355      */
356     public static List<String> getOptions() {
357         var options = new ArrayList<String>();
358         Iterable<String> args = ARGUMENTS.get();
359         if (args != null) {
360             args.forEach(options::add);
361         }
362         return options;
363     }
364 
365     /**
366      * Nothing to do.
367      */
368     @Override
369     public void flush() {}
370 
371     /**
372      * Nothing to do.
373      */
374     @Override
375     public void close() {}
376 }