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}