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 }