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.SourceVersion; 22 import javax.tools.JavaFileObject; 23 24 import java.nio.file.FileSystem; 25 import java.nio.file.Files; 26 import java.nio.file.Path; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Objects; 30 import java.util.Optional; 31 32 /** 33 * A single root directory of source files, associated with module name and release version. 34 * The module names are used when compiling a Module Source Hierarchy. 35 * The release version is used for multi-versions JAR files. 36 * 37 * <p>This class contains also the output directory, because this information is needed 38 * for determining whether a source file need to be recompiled.</p> 39 * 40 * @author Martin Desruisseaux 41 */ 42 final class SourceDirectory { 43 /** 44 * The module-info filename, without extension. 45 */ 46 static final String MODULE_INFO = "module-info"; 47 48 /** 49 * File suffix of source code. 50 */ 51 static final String JAVA_FILE_SUFFIX = ".java"; 52 53 /** 54 * File suffix of compiler classes. 55 */ 56 static final String CLASS_FILE_SUFFIX = ".class"; 57 58 /** 59 * The root directory of all source files. 60 */ 61 final Path root; 62 63 /** 64 * Kind of source files in this directory. This is usually {@link JavaFileObject.Kind#SOURCE}. 65 * This information is used for building a default include filter such as {@code "glob:*.java} 66 * if the user didn't specified an explicit filter. The default include filter may change for 67 * each root directory. 68 */ 69 final JavaFileObject.Kind fileKind; 70 71 /** 72 * Name of the module for which source directories are provided, or {@code null} if none. 73 * This name is supplied to the constructor instead of parsed from {@code module-info.java} 74 * file because the latter may not exist in this directory. For example, in a multi-release 75 * project the module-info may be declared in another directory for the base version. 76 */ 77 final String moduleName; 78 79 /** 80 * Path to the {@code module-info} file, or {@code null} if none. This flag is set when 81 * walking through the directory content. This is related, but not strictly equivalent, 82 * to whether the {@link #moduleName} is non-null. 83 */ 84 private Path moduleInfo; 85 86 /** 87 * The Java release for which source directories are provided, or {@code null} for the default release. 88 * This is used for multi-versions JAR files. 89 */ 90 final SourceVersion release; 91 92 /** 93 * The directory where to store the compilation results. 94 * This is the MOJO output directory with sub-directories appended according the following rules, in that order: 95 * 96 * <ol> 97 * <li>If {@link #moduleName} is non-null, then the module name is appended.</li> 98 * <li>If {@link #release} is non-null, then the next elements in the paths are 99 * {@code "META-INF/versions/<n>"} where {@code <n>} is the release number.</li> 100 * </ol> 101 */ 102 final Path outputDirectory; 103 104 /** 105 * Kind of output files in the output directory. 106 * This is usually {@link JavaFileObject.Kind#CLASS}. 107 */ 108 final JavaFileObject.Kind outputFileKind; 109 110 /** 111 * Creates a new source directory. 112 * 113 * @param root the root directory of all source files 114 * @param fileKind kind of source files in this directory (usually {@code SOURCE}) 115 * @param moduleName name of the module for which source directories are provided, or {@code null} if none 116 * @param release Java release for which source directories are provided, or {@code null} for the default release 117 * @param outputDirectory the directory where to store the compilation results 118 * @param outputFileKind Kind of output files in the output directory (usually {@ codeCLASS}) 119 */ 120 private SourceDirectory( 121 Path root, 122 JavaFileObject.Kind fileKind, 123 String moduleName, 124 SourceVersion release, 125 Path outputDirectory, 126 JavaFileObject.Kind outputFileKind) { 127 this.root = Objects.requireNonNull(root); 128 this.fileKind = Objects.requireNonNull(fileKind); 129 this.moduleName = moduleName; 130 this.release = release; 131 if (release != null) { 132 String version = release.name(); 133 version = version.substring(version.lastIndexOf('_') + 1); 134 FileSystem fs = outputDirectory.getFileSystem(); 135 Path subdir; 136 if (moduleName != null) { 137 subdir = fs.getPath(moduleName, "META-INF", "versions", version); 138 } else { 139 subdir = fs.getPath("META-INF", "versions", version); 140 } 141 outputDirectory = outputDirectory.resolve(subdir); 142 } else if (moduleName != null) { 143 outputDirectory = outputDirectory.resolve(moduleName); 144 } 145 this.outputDirectory = outputDirectory; 146 this.outputFileKind = outputFileKind; 147 } 148 149 /** 150 * Converts the given list of paths to a list of source directories. 151 * The returned list includes only the directories that exist. 152 * 153 * @param compileSourceRoots the root paths to source files 154 * @param outputDirectory the directory where to store the compilation results 155 * @return the given list of paths wrapped as source directory objects 156 */ 157 static List<SourceDirectory> fromPaths(List<Path> compileSourceRoots, Path outputDirectory) { 158 var roots = new ArrayList<SourceDirectory>(compileSourceRoots.size()); 159 for (Path p : compileSourceRoots) { 160 if (Files.exists(p)) { 161 // TODO: specify file kind, module name and release version. 162 roots.add(new SourceDirectory( 163 p, JavaFileObject.Kind.SOURCE, null, null, outputDirectory, JavaFileObject.Kind.CLASS)); 164 } 165 } 166 return roots; 167 } 168 169 /** 170 * Returns whether the given file is a {@code module-info.java} file. 171 * TODO: we could make this method non-static and verify that the given 172 * file is in the root of this directory. 173 */ 174 static boolean isModuleInfoSource(Path file) { 175 return (MODULE_INFO + JAVA_FILE_SUFFIX).equals(file.getFileName().toString()); 176 } 177 178 /** 179 * Invoked for each source files in this directory. 180 */ 181 void visit(Path sourceFile) { 182 if (isModuleInfoSource(sourceFile)) { 183 // Paranoiac check: only one file should exist, but if many, keep the one closest to the root. 184 if (moduleInfo == null || moduleInfo.getNameCount() >= sourceFile.getNameCount()) { 185 moduleInfo = sourceFile; 186 } 187 } 188 } 189 190 /** 191 * Path to the {@code module-info.java} source file, or empty if none. 192 * This information is accurate only after {@link PathFilter} finished 193 * to walk through all source files in a directory. 194 */ 195 public Optional<Path> getModuleInfo() { 196 return Optional.ofNullable(moduleInfo); 197 } 198 199 /** 200 * Compares the given object with this source directory for equality. 201 * 202 * @param obj the object to compare 203 * @return whether the two objects have the same path, module name and release version 204 */ 205 @Override 206 public boolean equals(Object obj) { 207 if (obj instanceof SourceDirectory other) { 208 return release == other.release 209 && Objects.equals(moduleName, other.moduleName) 210 && root.equals(other.root) 211 && outputDirectory.equals(other.outputDirectory); 212 } 213 return false; 214 } 215 216 /** 217 * {@return a hash code value for this root directory}. 218 */ 219 @Override 220 public int hashCode() { 221 return root.hashCode() + 7 * Objects.hash(moduleName, release); 222 } 223 224 /** 225 * {@return a string representation of this root directory for debugging purposes}. 226 */ 227 @Override 228 public String toString() { 229 var sb = new StringBuilder(100).append('"').append(root).append('"'); 230 if (moduleName != null) { 231 sb.append(" for module \"").append(moduleName).append('"'); 232 } 233 if (release != null) { 234 sb.append(" on Java release ").append(release); 235 } 236 return sb.toString(); 237 } 238 }