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 java.io.IOException;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  
25  import static org.apache.maven.plugin.compiler.SourceDirectory.CLASS_FILE_SUFFIX;
26  import static org.apache.maven.plugin.compiler.SourceDirectory.JAVA_FILE_SUFFIX;
27  import static org.apache.maven.plugin.compiler.SourceDirectory.MODULE_INFO;
28  
29  /**
30   * Helper class for the case where a {@code module-info.java} file defined in the tests
31   * overwrites the file defined in the main classes. It should be a last-resort practice only,
32   * when options such as {@code --add-reads} or {@code --add-exports} are not sufficient.
33   *
34   * <p>The code in this class is useful only when {@link AbstractCompilerMojo#SUPPORT_LEGACY} is true.
35   * This class can be fully deleted if a future version permanently set above-cited flag to false.</p>
36   */
37  final class ModuleInfoOverwrite implements Runnable {
38      /**
39       * Path to the original {@code module-info.java} file. It will need to be temporarily renamed
40       * because otherwise the Java compiler seems to unconditionally compiles it, even if we do not
41       * specify this file in the list of sources to compile.
42       */
43      private final Path testSourceFile;
44  
45      /**
46       * Path to the {@code module-info.java.bak} file.
47       */
48      private final Path savedSourceFile;
49  
50      /**
51       * Path to the main {@code module-info.class} file to temporarily hide.
52       * This file will be temporarily renamed to {@link #moduleInfoBackup}
53       * before to compile the tests.
54       */
55      private final Path moduleInfoToHide;
56  
57      /**
58       * Path to the renamed main {@code module-info.class} file. This file
59       * needs to be renamed as {@link #moduleInfoToHide} after compilation.
60       */
61      private final Path moduleInfoBackup;
62  
63      /**
64       * The {@code module-info.class} to use as a replacement for the one which has been renamed.
65       */
66      private final Path moduleInfoReplacement;
67  
68      /**
69       * The shutdown hook invoked if the user interrupts the compilation, for example with [Control-C].
70       */
71      private Thread shutdownHook;
72  
73      /**
74       * Creates a new instance.
75       */
76      private ModuleInfoOverwrite(Path source, Path main, Path test) {
77          testSourceFile = source;
78          savedSourceFile = source.resolveSibling(MODULE_INFO + JAVA_FILE_SUFFIX + ".bak");
79          moduleInfoToHide = main;
80          moduleInfoBackup = main.resolveSibling(MODULE_INFO + CLASS_FILE_SUFFIX + ".bak");
81          moduleInfoReplacement = test;
82      }
83  
84      /**
85       * Returns an instance for the given main output directory, or {@code null} if not needed.
86       * This method should be invoked only if a {@code module-info.java} file exists and may
87       * overwrite a file defined in the main classes.
88       */
89      static ModuleInfoOverwrite create(Path source, Path mainOutputDirectory, Path testOutputDirectory)
90              throws IOException {
91          Path main = mainOutputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX);
92          if (Files.isRegularFile(main)) {
93              Path test = testOutputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX);
94              if (Files.isRegularFile(test)) {
95                  var mo = new ModuleInfoOverwrite(source, main, test);
96                  mo.substitute();
97                  return mo;
98              }
99          }
100         return null;
101     }
102 
103     /**
104      * Replaces the main {@code module-info.class} file by the test one.
105      * The original file is saved in the {@code module-info.class.bak} file.
106      * Then the test {@code module-info.class} is moved to the main directory.
107      * Note that it needs to be moved, not copied or linked, because we need
108      * to temporarily remove {@code module-info.class} from the test directory
109      * (otherwise {@code javac} does not seem to consider that we are patching a module).
110      *
111      * @throws IOException if an error occurred while renaming the file.
112      */
113     private void substitute() throws IOException {
114         Files.move(testSourceFile, savedSourceFile);
115         Files.move(moduleInfoToHide, moduleInfoBackup);
116         Files.move(moduleInfoReplacement, moduleInfoToHide);
117         if (shutdownHook == null) { // Paranoiac check in case this method is invoked twice (should not happen).
118             shutdownHook = new Thread(this);
119             Runtime.getRuntime().addShutdownHook(shutdownHook);
120         }
121     }
122 
123     /**
124      * Restores the {@code module-info} file.
125      *
126      * @throws IOException if an error occurred while renaming the file.
127      */
128     void restore() throws IOException {
129         if (shutdownHook != null) {
130             Runtime.getRuntime().removeShutdownHook(shutdownHook);
131         }
132         Files.move(savedSourceFile, testSourceFile); // File to restore in priority.
133         Files.move(moduleInfoToHide, moduleInfoReplacement);
134         Files.move(moduleInfoBackup, moduleInfoToHide);
135     }
136 
137     /**
138      * Invoked during JVM shutdown if user interrupted the compilation, for example with [Control-C].
139      */
140     @Override
141     @SuppressWarnings("CallToPrintStackTrace")
142     public void run() {
143         shutdownHook = null;
144         try {
145             restore();
146         } catch (IOException e) {
147             // We cannot do much better because the loggers are shutting down.
148             e.printStackTrace();
149         }
150     }
151 }