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