001/* 002 * Copyright (C) 2022 - 2024, the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.github.ascopes.jct.filemanagers; 017 018import io.github.ascopes.jct.containers.ModuleContainerGroup; 019import io.github.ascopes.jct.containers.OutputContainerGroup; 020import io.github.ascopes.jct.containers.PackageContainerGroup; 021import io.github.ascopes.jct.workspaces.PathRoot; 022import io.github.ascopes.jct.workspaces.Workspace; 023import java.io.IOException; 024import java.util.Collection; 025import java.util.Iterator; 026import java.util.Set; 027import javax.tools.FileObject; 028import javax.tools.JavaFileManager; 029import javax.tools.JavaFileObject; 030import javax.tools.JavaFileObject.Kind; 031import javax.tools.StandardLocation; 032import org.apiguardian.api.API; 033import org.apiguardian.api.API.Status; 034import org.jspecify.annotations.Nullable; 035 036/** 037 * Extension around a {@link JavaFileManager} that allows adding of {@link PathRoot} objects to the 038 * manager. 039 * 040 * <p>This component is responsible for bridging the gap between a {@link Workspace} and 041 * a {@link javax.tools.JavaCompiler} when performing a compilation, and thus includes a number of 042 * required operations that the compiler will query the file system with. In addition, this 043 * interface also defines a number of additional functionalities that are useful for querying and 044 * verifying the outcome of compilations within tests. 045 * 046 * @author Ashley Scopes 047 * @since 0.0.1 048 */ 049@API(since = "0.0.1", status = Status.STABLE) 050public interface JctFileManager extends JavaFileManager { 051 052 /** 053 * Add a package-oriented path to a given location. 054 * 055 * <p>To add a module, first obtain the module location using 056 * {@link #getLocationForModule(Location, String)}, and pass that result to this call. 057 * 058 * @param location the location to use. 059 * @param path the path to add. 060 * @see #getLocationForModule(Location, String) 061 */ 062 void addPath(Location location, PathRoot path); 063 064 /** 065 * Add a collection of package-oriented paths to a given location. 066 * 067 * <p>To add a module, first obtain the module location using 068 * {@link #getLocationForModule(Location, String)}, and pass that result to this call. 069 * 070 * @param location the location to use. 071 * @param paths the paths to add. 072 * @see #getLocationForModule(Location, String) 073 */ 074 void addPaths(Location location, Collection<? extends PathRoot> paths); 075 076 /** 077 * Close this file manager. 078 * 079 * <p>If the file manager is already closed, this will have no effect. 080 * 081 * <p>JCT File managers may choose to not implement this operation if not applicable. 082 * 083 * @throws IOException if an IO error occurs during closure. 084 */ 085 @Override 086 void close() throws IOException; 087 088 /** 089 * Determine if the given file object resides in the given location. 090 * 091 * <p>If package-oriented, then the file must exist in the given location directly. 092 * 093 * <p>If module-oriented, then the file must exist in one of the modules within the location. 094 * This implies that {@link #getLocationForModule(Location, JavaFileObject)} would also return 095 * a valid non-null value. 096 * 097 * @param location the location. 098 * @param fileObject the file object. 099 * @return {@code true} if the file exists in the given location, or {@code false} otherwise. 100 * @throws IOException if an IO error occurs during the lookup. 101 */ 102 @Override 103 boolean contains(Location location, FileObject fileObject) throws IOException; 104 105 /** 106 * Copy all containers from the first location to the second location. 107 * 108 * @param from the first location. 109 * @param to the second location. 110 */ 111 void copyContainers(Location from, Location to); 112 113 /** 114 * Register an empty container for the given location to indicate to the compiler that the feature 115 * exists, but has no configured paths. 116 * 117 * <p>This is needed to coerce the behaviour for annotation processing in some cases. 118 * 119 * <p>If the location already exists, then do not do anything. 120 * 121 * <p>If the location is an output location, then this operation does not make any sense, since 122 * an empty location cannot have files output to it. In this case, you will likely get an 123 * exception. 124 * 125 * <p>Likewise, this operation does not make sense for module locations within a module-oriented 126 * location group, so this operation will fail with an error for those inputs as well. 127 * 128 * @param location the location to apply an empty container for. 129 * @throws IllegalArgumentException if the location is an output location or a module location. 130 */ 131 void createEmptyLocation(Location location); 132 133 /** 134 * Flush this file manager. 135 * 136 * <p>If the file manager is already closed, this will have no effect. 137 * 138 * <p>JCT File managers may choose to not implement this operation if not applicable. 139 * 140 * @throws IOException if an IO error occurs during flushing. 141 */ 142 @Override 143 void flush() throws IOException; 144 145 /** 146 * Get the class loader for loading classes for the given location. 147 * 148 * @param location the location to fetch a class loader for. 149 * @return a class loader, or {@code null} if loading classes for the given location is not 150 * supported. 151 * @throws SecurityException if a class loader can not be created in the current security 152 * context 153 * @throws IllegalStateException if {@link #close} has been called and this file manager cannot 154 * be reopened 155 * @throws IllegalArgumentException if the location is a module-oriented location 156 */ 157 @Nullable 158 @Override 159 ClassLoader getClassLoader(Location location); 160 161 /** 162 * Get the associated effective release. 163 * 164 * @return the effective release. 165 * @since 0.0.1 166 */ 167 String getEffectiveRelease(); 168 169 /** 170 * Get a file for input operations. 171 * 172 * @param location a package-oriented location. 173 * @param packageName a package name. 174 * @param relativeName a relative name. 175 * @return the file object, or {@code null} if not found or not available. 176 * @throws IOException if an IO error occurs reading the file details. 177 * @throws IllegalArgumentException if the location is not a package-oriented location. 178 */ 179 @Nullable 180 @Override 181 FileObject getFileForInput( 182 Location location, 183 String packageName, 184 String relativeName 185 ) throws IOException; 186 187 /** 188 * Get a file for output operations. 189 * 190 * @param location an output location. 191 * @param packageName a package name. 192 * @param relativeName a relative name. 193 * @param sibling a file object to be used as hint for placement, might be {@code null}. 194 * @return the file object, or {@code null} if not found or not available. 195 * @throws IOException if an IO error occurs reading the file details. 196 * @throws IllegalArgumentException if the location is not an output location. 197 */ 198 @Nullable 199 @Override 200 FileObject getFileForOutput( 201 Location location, 202 String packageName, 203 String relativeName, 204 @Nullable FileObject sibling 205 ) throws IOException; 206 207 /** 208 * Get a Java file for input operations. 209 * 210 * @param location a package-oriented location. 211 * @param className the name of a class. 212 * @param kind the kind of file. 213 * @return the file object, or {@code null} if not found or not available. 214 * @throws IOException if an IO error occurs reading the file details. 215 * @throws IllegalArgumentException if the location is not a package-oriented location. 216 */ 217 @Nullable 218 @Override 219 JavaFileObject getJavaFileForInput( 220 Location location, 221 String className, 222 Kind kind 223 ) throws IOException; 224 225 /** 226 * Get a Java file for output operations. 227 * 228 * @param location an output location. 229 * @param className the name of a class. 230 * @param kind the kind of file. 231 * @param sibling a file object to be used as hint for placement, might be {@code null}. 232 * @return the file object, or {@code null} if not found or not available. 233 * @throws IOException if an IO error occurs reading the file details. 234 * @throws IllegalArgumentException if the location is not an output location. 235 */ 236 @Nullable 237 @Override 238 JavaFileObject getJavaFileForOutput( 239 Location location, 240 String className, 241 Kind kind, 242 @Nullable FileObject sibling 243 ) throws IOException; 244 245 /** 246 * Get the location for a named module within the given location. 247 * 248 * @param location the module-oriented location. 249 * @param moduleName the name of the module to be found. 250 * @return the location of the named module, or {@code null} if not found. 251 * @throws IOException if an IO error occurs during resolution. 252 * @throws IllegalArgumentException if the location is neither an output location nor a 253 * module-oriented location. 254 */ 255 @Nullable 256 @Override 257 Location getLocationForModule(Location location, String moduleName) throws IOException; 258 259 /** 260 * Get the location for the module holding a given file object within the given location. 261 * 262 * @param location the module-oriented location. 263 * @param fileObject the file object to resolve the module for. 264 * @return the location, or {@code null} if not found. 265 * @throws IOException if an IO error occurs during resolution. 266 * @throws IllegalArgumentException if the location is neither an output location nor a 267 * module-oriented location. 268 */ 269 @Nullable 270 @Override 271 Location getLocationForModule(Location location, JavaFileObject fileObject) throws IOException; 272 273 /** 274 * Get the container group for the given package-oriented location. 275 * 276 * @param location the package oriented location. 277 * @return the container group, or null if one does not exist. 278 */ 279 @Nullable 280 PackageContainerGroup getPackageContainerGroup(Location location); 281 282 /** 283 * Get a collection of all package container groups in this file manager. 284 * 285 * @return the package container groups. 286 */ 287 Collection<PackageContainerGroup> getPackageContainerGroups(); 288 289 /** 290 * Get the container group for the given module-oriented location. 291 * 292 * @param location the module oriented location. 293 * @return the container group, or null if one does not exist. 294 */ 295 @Nullable 296 ModuleContainerGroup getModuleContainerGroup(Location location); 297 298 /** 299 * Get a collection of all module container groups in this file manager. 300 * 301 * @return the module container groups. 302 */ 303 Collection<ModuleContainerGroup> getModuleContainerGroups(); 304 305 /** 306 * Get the container group for the given output-oriented location. 307 * 308 * @param location the output oriented location. 309 * @return the container group, or null if one does not exist. 310 */ 311 @Nullable 312 OutputContainerGroup getOutputContainerGroup(Location location); 313 314 /** 315 * Get a collection of all output container groups in this file manager. 316 * 317 * @return the output container groups. 318 */ 319 Collection<OutputContainerGroup> getOutputContainerGroups(); 320 321 /** 322 * Handles one option. If {@code current} is an option to this file manager it will consume any 323 * arguments to that option from {@code remaining} and return true, otherwise return false. 324 * 325 * @param current current option. 326 * @param remaining remaining options. 327 * @return {@code true} if this option was handled by this file manager, {@code false} otherwise. 328 * @throws IllegalArgumentException if this option to this file manager is used incorrectly. 329 * @throws IllegalStateException if {@link #close} has been called and this file manager cannot 330 * be reopened. 331 */ 332 boolean handleOption(String current, Iterator<String> remaining); 333 334 /** 335 * Determines if a location is known to this file manager. 336 * 337 * @param location a location. 338 * @return true if the location is known to this file manager. 339 */ 340 boolean hasLocation(Location location); 341 342 /** 343 * Infers a binary name of a file object based on a package-oriented location. The binary name 344 * returned might not be a valid binary name according to the Java Language Specification. 345 * 346 * @param location a location. 347 * @param file a file object. 348 * @return a binary name or {@code null} the file object is not found in the given location 349 * @throws IllegalArgumentException if the location is a module-oriented location. 350 * @throws IllegalStateException if {@link #close} has been called and this file manager cannot 351 * be reopened. 352 */ 353 @Nullable 354 @Override 355 String inferBinaryName(Location location, JavaFileObject file); 356 357 /** 358 * Infer the name of the module from its location, as returned by {@code getLocationForModule} or 359 * {@code listModuleLocations}. 360 * 361 * @param location a package-oriented location representing a module. 362 * @return the name of the module, or {@code null} if the name cannot be resolved or does not 363 * exist. 364 * @throws IOException if an I/O error occurred during resolution. 365 * @throws IllegalArgumentException if the location is not one known to this file manager. 366 */ 367 @Nullable 368 @Override 369 String inferModuleName(Location location) throws IOException; 370 371 /** 372 * Determine if the two file objects are the same file. 373 * 374 * @param a a file object, can be {@code null}. 375 * @param b a file object, can be {@code null}. 376 * @return {@code true} if both arguments are non-null and refer to the same logical file, or 377 * {@code false} otherwise. 378 */ 379 @Override 380 boolean isSameFile(@Nullable FileObject a, @Nullable FileObject b); 381 382 /** 383 * List all file objects in the given location matching the given criteria. 384 * 385 * @param location the location to search in. 386 * @param packageName the package name to search in, or {@code ""} to search in the root 387 * location. 388 * @param kinds the kinds of file to return, or {@link Set#of} {@code (} {@link Kind#OTHER} 389 * {@code )} to find all files. 390 * @param recurse {@code true} to recurse into subpackages, {@code false} to only check the 391 * current package. 392 * @return a collection of unique file objects that were found. 393 * @throws IOException if an IO error occurs reading the file system. 394 * @throws IllegalArgumentException if the location is a module-oriented location 395 * @throws IllegalStateException if {@link #close} has been called and this file manager cannot 396 * be reopened 397 */ 398 @Override 399 Set<JavaFileObject> list( 400 Location location, 401 String packageName, 402 Set<Kind> kinds, 403 boolean recurse 404 ) throws IOException; 405 406 /** 407 * List all module-based locations for the given module-oriented or output location. 408 * 409 * @param location the module-oriented/output location for which to list the modules. 410 * @return the iterable of sets of modules. 411 * @throws IOException if an IO error occurs during resolution. 412 * @throws IllegalArgumentException if the location is not a module-oriented or output location. 413 */ 414 @Override 415 Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException; 416 417 /// 418 /// Default helper overrides 419 /// 420 421 /** 422 * Get the location holding the {@link StandardLocation#CLASS_OUTPUT class outputs}. 423 * 424 * @return the location, or {@code null} if the location is not present in the file manager. 425 * @since 0.1.0 426 */ 427 @Nullable 428 default OutputContainerGroup getClassOutputGroup() { 429 return getOutputContainerGroup(StandardLocation.CLASS_OUTPUT); 430 } 431 432 /** 433 * Get the location holding the {@link StandardLocation#SOURCE_OUTPUT source outputs}. 434 * 435 * @return the location, or {@code null} if the location is not present in the file manager. 436 * @since 0.1.0 437 */ 438 @Nullable 439 default OutputContainerGroup getSourceOutputGroup() { 440 return getOutputContainerGroup(StandardLocation.SOURCE_OUTPUT); 441 } 442 443 /** 444 * Get the location holding the {@link StandardLocation#CLASS_PATH class path}. 445 * 446 * @return the location, or {@code null} if the location is not present in the file manager. 447 * @since 0.1.0 448 */ 449 @Nullable 450 default PackageContainerGroup getClassPathGroup() { 451 return getPackageContainerGroup(StandardLocation.CLASS_PATH); 452 } 453 454 /** 455 * Get the location holding the {@link StandardLocation#SOURCE_PATH source path}. 456 * 457 * @return the location, or {@code null} if the location is not present in the file manager. 458 * @since 0.1.0 459 */ 460 @Nullable 461 default PackageContainerGroup getSourcePathGroup() { 462 return getPackageContainerGroup(StandardLocation.SOURCE_PATH); 463 } 464 465 /** 466 * Get the location holding the 467 * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}. 468 * 469 * @return the location, or {@code null} if the location is not present in the file manager. 470 * @since 0.1.0 471 */ 472 @Nullable 473 default PackageContainerGroup getAnnotationProcessorPathGroup() { 474 return getPackageContainerGroup(StandardLocation.ANNOTATION_PROCESSOR_PATH); 475 } 476 477 /** 478 * Get the location holding the 479 * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}. 480 * 481 * @return the location, or {@code null} if the location is not present in the file manager. 482 * @since 0.1.0 483 */ 484 @Nullable 485 default ModuleContainerGroup getAnnotationProcessorModulePathGroup() { 486 return getModuleContainerGroup(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH); 487 } 488 489 /** 490 * Get the location holding the {@link StandardLocation#MODULE_SOURCE_PATH module source path}. 491 * 492 * @return the location, or {@code null} if the location is not present in the file manager. 493 * @since 0.1.0 494 */ 495 @Nullable 496 default ModuleContainerGroup getModuleSourcePathGroup() { 497 return getModuleContainerGroup(StandardLocation.MODULE_SOURCE_PATH); 498 } 499 500 /** 501 * Get the location holding the {@link StandardLocation#MODULE_PATH module path}. 502 * 503 * @return the location, or {@code null} if the location is not present in the file manager. 504 * @since 0.1.0 505 */ 506 @Nullable 507 default ModuleContainerGroup getModulePathGroup() { 508 return getModuleContainerGroup(StandardLocation.MODULE_PATH); 509 } 510}