001/* 002 * Copyright (C) 2022 - 2023, 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.workspaces; 017 018import io.github.ascopes.jct.filemanagers.ModuleLocation; 019import java.io.UncheckedIOException; 020import java.nio.file.Path; 021import java.util.List; 022import java.util.Map; 023import javax.tools.JavaFileManager.Location; 024import javax.tools.StandardLocation; 025import org.apiguardian.api.API; 026import org.apiguardian.api.API.Status; 027 028/** 029 * Interface for a Workspace to hold files and directories within. 030 * 031 * <p>This acts as a nexus for managing the lifetime of test sources and directories, 032 * and should be used within a try-with-resources block to ensure temporary files get released after 033 * the test completes. 034 * 035 * @author Ashley Scopes 036 * @since 0.0.1 037 */ 038@API(since = "0.0.1", status = Status.STABLE) 039public interface Workspace extends AutoCloseable { 040 041 /** 042 * Add an existing package root to this workspace and associate it with the given location. 043 * 044 * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException} 045 * will be thrown: 046 * 047 * <ul> 048 * <li> 049 * The {@code location} must not be 050 * {@link Location#isModuleOrientedLocation() module-oriented}. 051 * </li> 052 * <li> 053 * The {@code path} must exist. 054 * </li> 055 * </ul> 056 * 057 * @param location the location to associate with. 058 * @param path the path to add. 059 * @throws IllegalArgumentException if the inputs are invalid. 060 */ 061 void addPackage(Location location, Path path); 062 063 /** 064 * Add an existing package root to this workspace and associate it with the given module name in 065 * the given location. 066 * 067 * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException} 068 * will be thrown: 069 * 070 * <ul> 071 * <li> 072 * The {@code location} must be {@link Location#isModuleOrientedLocation() module-oriented} 073 * or an {@link Location#isOutputLocation() output location}. 074 * </li> 075 * <li> 076 * The {@code location} must not be a {@link ModuleLocation module-location handle} already. 077 * </li> 078 * <li> 079 * The {@code path} must exist. 080 * </li> 081 * </ul> 082 * 083 * @param location the location to associate with. 084 * @param moduleName the name of the module. 085 * @param path the path to add. 086 * @throws IllegalArgumentException if the inputs are invalid. 087 */ 088 void addModule(Location location, String moduleName, Path path); 089 090 /** 091 * Add a package to the class outputs. 092 * 093 * @param path the path to add. 094 * @throws IllegalArgumentException if the path does not exist. 095 */ 096 default void addClassOutputPackage(Path path) { 097 addPackage(StandardLocation.CLASS_OUTPUT, path); 098 } 099 100 /** 101 * Add a module to the class outputs. 102 * 103 * @param moduleName the module name. 104 * @param path the path to add. 105 * @throws IllegalArgumentException if the path does not exist. 106 */ 107 default void addClassOutputModule(String moduleName, Path path) { 108 addModule(StandardLocation.CLASS_OUTPUT, moduleName, path); 109 } 110 111 /** 112 * Add a package to the source outputs. 113 * 114 * @param path the path to add. 115 * @throws IllegalArgumentException if the path does not exist. 116 */ 117 default void addSourceOutputPackage(Path path) { 118 addPackage(StandardLocation.SOURCE_OUTPUT, path); 119 } 120 121 /** 122 * Add a module to the source outputs. 123 * 124 * @param moduleName the module name. 125 * @param path the path to add. 126 * @throws IllegalArgumentException if the path does not exist. 127 */ 128 default void addSourceOutputModule(String moduleName, Path path) { 129 addModule(StandardLocation.SOURCE_OUTPUT, moduleName, path); 130 } 131 132 /** 133 * Add a package to the class path. 134 * 135 * @param path the path to add. 136 * @throws IllegalArgumentException if the path does not exist. 137 */ 138 default void addClassPathPackage(Path path) { 139 addPackage(StandardLocation.CLASS_PATH, path); 140 } 141 142 /** 143 * Add a module to the module path. 144 * 145 * @param moduleName the module name. 146 * @param path the path to add. 147 * @throws IllegalArgumentException if the path does not exist. 148 */ 149 default void addModulePathModule(String moduleName, Path path) { 150 addModule(StandardLocation.MODULE_PATH, moduleName, path); 151 } 152 153 /** 154 * Add a package to the source path. 155 * 156 * @param path the path to add. 157 * @throws IllegalArgumentException if the path does not exist. 158 */ 159 default void addSourcePathPackage(Path path) { 160 addPackage(StandardLocation.SOURCE_PATH, path); 161 } 162 163 /** 164 * Add a module to the source path. 165 * 166 * <p>Note that this will signal to the compiler to run in multi-module compilation mode. 167 * Any source packages that were {@link #addSourcePathPackage(Path) added} or 168 * {@link #createSourcePathPackage()} created} will be ignored. 169 * 170 * <p>If you are using a single-module, you can 171 * {@link #addSourcePathPackage(Path) add a source package} and include a {@code module-info.java} 172 * in the base directory instead. 173 * 174 * @param moduleName the module name. 175 * @param path the path to add. 176 * @throws IllegalArgumentException if the path does not exist. 177 */ 178 default void addSourcePathModule(String moduleName, Path path) { 179 addModule(StandardLocation.MODULE_SOURCE_PATH, moduleName, path); 180 } 181 182 /** 183 * Add a package to the annotation processor path. 184 * 185 * <p>Note that this will be ignored if the compiler is provided with explicit annotation 186 * processor instances to run. 187 * 188 * @param path the path to add. 189 * @throws IllegalArgumentException if the path does not exist. 190 */ 191 default void addAnnotationProcessorPathPackage(Path path) { 192 addPackage(StandardLocation.ANNOTATION_PROCESSOR_PATH, path); 193 } 194 195 /** 196 * Add a module to the annotation processor path. 197 * 198 * <p>Note that this will be ignored if the compiler is provided with explicit annotation 199 * processor instances to run. 200 * 201 * @param moduleName the module name. 202 * @param path the path to add. 203 * @throws IllegalArgumentException if the path does not exist. 204 */ 205 default void addAnnotationProcessorPathModule(String moduleName, Path path) { 206 addModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName, path); 207 } 208 209 /** 210 * Add a package to the platform class path (also known as the boot class path). 211 * 212 * @param path the path to add. 213 * @throws IllegalArgumentException if the path does not exist. 214 */ 215 default void addPlatformClassPathPackage(Path path) { 216 addPackage(StandardLocation.PLATFORM_CLASS_PATH, path); 217 } 218 219 /** 220 * Add a package to the native header outputs. 221 * 222 * @param path the path to add. 223 * @throws IllegalArgumentException if the path does not exist. 224 */ 225 default void addNativeHeaderOutputPackage(Path path) { 226 addPackage(StandardLocation.NATIVE_HEADER_OUTPUT, path); 227 } 228 229 /** 230 * Add a module to the native header outputs. 231 * 232 * @param moduleName the name of the module. 233 * @param path the path to add. 234 * @throws IllegalArgumentException if the path does not exist. 235 */ 236 default void addNativeHeaderOutputModule(String moduleName, Path path) { 237 addModule(StandardLocation.NATIVE_HEADER_OUTPUT, moduleName, path); 238 } 239 240 /** 241 * Add a module to the upgrade module path. 242 * 243 * @param moduleName the module name. 244 * @param path the path to add. 245 * @throws IllegalArgumentException if the path does not exist. 246 */ 247 default void addUpgradeModulePathModule(String moduleName, Path path) { 248 addModule(StandardLocation.UPGRADE_MODULE_PATH, moduleName, path); 249 } 250 251 /** 252 * Add a module to the system module path. 253 * 254 * @param moduleName the module name. 255 * @param path the path to add. 256 * @throws IllegalArgumentException if the path does not exist. 257 */ 258 default void addSystemModulePathModule(String moduleName, Path path) { 259 addModule(StandardLocation.SYSTEM_MODULES, moduleName, path); 260 } 261 262 /** 263 * Add a module to the patch module path. 264 * 265 * @param moduleName the module name. 266 * @param path the path to add. 267 * @throws IllegalArgumentException if the path does not exist. 268 */ 269 default void addPatchModulePathModule(String moduleName, Path path) { 270 addModule(StandardLocation.PATCH_MODULE_PATH, moduleName, path); 271 } 272 273 /** 274 * Attempt to close all resources in this workspace. 275 * 276 * @throws UncheckedIOException if an error occurs. 277 */ 278 @Override 279 void close(); 280 281 /** 282 * Create a new test directory for a package root and associate it with the given location. 283 * 284 * <p>This path will be destroyed when the workspace is {@link #close() closed}. 285 * 286 * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException} 287 * will be thrown: 288 * 289 * <ul> 290 * <li> 291 * The {@code location} must not be 292 * {@link Location#isModuleOrientedLocation() module-oriented}. 293 * </li> 294 * </ul> 295 * 296 * @param location the location to associate with. 297 * @return the test directory that was created. 298 * @throws IllegalArgumentException if the inputs are invalid. 299 */ 300 ManagedDirectory createPackage(Location location); 301 302 /** 303 * Create a new test directory for a package root and associate it with the given module name in 304 * the given location. 305 * 306 * <p>This path will be destroyed when the workspace is {@link #close() closed}. 307 * 308 * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException} 309 * will be thrown: 310 * 311 * <ul> 312 * <li> 313 * The {@code location} must be {@link Location#isModuleOrientedLocation() module-oriented} 314 * or an {@link Location#isOutputLocation() output location}. 315 * </li> 316 * <li> 317 * The {@code location} must not be a {@link ModuleLocation module-location handle} already. 318 * </li> 319 * </ul> 320 * 321 * @param location the location to associate with. 322 * @param moduleName the module name to use. 323 * @return the test directory that was created. 324 * @throws IllegalArgumentException if the inputs are invalid. 325 */ 326 ManagedDirectory createModule(Location location, String moduleName); 327 328 /** 329 * Create a package in the class outputs. 330 * 331 * @return the created test directory. 332 */ 333 default ManagedDirectory createClassOutputPackage() { 334 return createPackage(StandardLocation.CLASS_OUTPUT); 335 } 336 337 /** 338 * Create a module in the class outputs. 339 * 340 * @param moduleName the module name. 341 * @return the created test directory. 342 */ 343 default ManagedDirectory createClassOutputModule(String moduleName) { 344 return createModule(StandardLocation.CLASS_OUTPUT, moduleName); 345 } 346 347 /** 348 * Create a package in the source outputs. 349 * 350 * @return the created test directory. 351 */ 352 default ManagedDirectory createSourceOutputPackage() { 353 return createPackage(StandardLocation.SOURCE_OUTPUT); 354 } 355 356 /** 357 * Create a module in the source outputs. 358 * 359 * @param moduleName the module name. 360 * @return the created test directory. 361 */ 362 default ManagedDirectory createSourceOutputModule(String moduleName) { 363 return createModule(StandardLocation.SOURCE_OUTPUT, moduleName); 364 } 365 366 /** 367 * Create a package in the class path. 368 * 369 * @return the created test directory. 370 */ 371 default ManagedDirectory createClassPathPackage() { 372 return createPackage(StandardLocation.CLASS_PATH); 373 } 374 375 /** 376 * Create a module in the module path. 377 * 378 * @param moduleName the module name. 379 * @return the created test directory. 380 */ 381 default ManagedDirectory createModulePathModule(String moduleName) { 382 return createModule(StandardLocation.MODULE_PATH, moduleName); 383 } 384 385 /** 386 * Create a package in the source path. 387 * 388 * @return the created test directory. 389 */ 390 default ManagedDirectory createSourcePathPackage() { 391 return createPackage(StandardLocation.SOURCE_PATH); 392 } 393 394 /** 395 * Create a module in the source path. 396 * 397 * <p>Note that this will signal to the compiler to run in multi-module compilation mode. 398 * Any source packages that were {@link #createSourcePathPackage() created} or 399 * {@link #addSourcePathPackage(Path) added} will be ignored. 400 * 401 * <p>If you are using a single-module, you can 402 * {@link #createSourcePathPackage() create a source package} and include a 403 * {@code module-info.java} in the base directory instead. 404 * 405 * @param moduleName the module name. 406 * @return the created test directory. 407 */ 408 default ManagedDirectory createSourcePathModule(String moduleName) { 409 return createModule(StandardLocation.MODULE_SOURCE_PATH, moduleName); 410 } 411 412 /** 413 * Create a package in the annotation processor path. 414 * 415 * <p>Note that this will be ignored if the compiler is provided with explicit annotation 416 * processor instances to run. 417 * 418 * @return the created test directory. 419 */ 420 default ManagedDirectory createAnnotationProcessorPathPackage() { 421 return createPackage(StandardLocation.ANNOTATION_PROCESSOR_PATH); 422 } 423 424 /** 425 * Create a module in the annotation processor path. 426 * 427 * <p>Note that this will be ignored if the compiler is provided with explicit annotation 428 * processor instances to run. 429 * 430 * @param moduleName the module name. 431 * @return the created test directory. 432 */ 433 default ManagedDirectory createAnnotationProcessorPathModule(String moduleName) { 434 return createModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName); 435 } 436 437 /** 438 * Create a package in the platform class path (also known as the boot class path). 439 * 440 * @return the created test directory. 441 */ 442 default ManagedDirectory createPlatformClassPathPackage() { 443 return createPackage(StandardLocation.PLATFORM_CLASS_PATH); 444 } 445 446 /** 447 * Create a package in the native header outputs. 448 * 449 * @return the created test directory. 450 */ 451 default ManagedDirectory createNativeHeaderOutputPackage() { 452 return createPackage(StandardLocation.NATIVE_HEADER_OUTPUT); 453 } 454 455 /** 456 * Create a module in the native header outputs. 457 * 458 * @param moduleName the module name. 459 * @return the created test directory. 460 */ 461 default ManagedDirectory createNativeHeaderOutputModule(String moduleName) { 462 return createModule(StandardLocation.NATIVE_HEADER_OUTPUT, moduleName); 463 } 464 465 /** 466 * Create a module in the upgrade module path. 467 * 468 * @param moduleName the module name. 469 * @return the created test directory. 470 */ 471 default ManagedDirectory createUpgradeModulePathModule(String moduleName) { 472 return createModule(StandardLocation.UPGRADE_MODULE_PATH, moduleName); 473 } 474 475 /** 476 * Create a module in the system module path. 477 * 478 * @param moduleName the module name. 479 * @return the created test directory. 480 */ 481 default ManagedDirectory createSystemModulePathModule(String moduleName) { 482 return createModule(StandardLocation.SYSTEM_MODULES, moduleName); 483 } 484 485 /** 486 * Create a module in the patch module path. 487 * 488 * @param moduleName the module name. 489 * @return the created test directory. 490 */ 491 default ManagedDirectory createPatchModulePathModule(String moduleName) { 492 return createModule(StandardLocation.PATCH_MODULE_PATH, moduleName); 493 } 494 495 /** 496 * Get an immutable copy of the current paths to operate on. 497 * 498 * @return the paths. 499 */ 500 Map<Location, List<? extends PathRoot>> getAllPaths(); 501 502 /** 503 * Get the path strategy in use. 504 * 505 * @return the path strategy. 506 */ 507 PathStrategy getPathStrategy(); 508}