001/*
002 * Copyright (C) 2022 - 2025, 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.JctFileManager;
019import io.github.ascopes.jct.filemanagers.ModuleLocation;
020import java.io.IOException;
021import java.io.UncheckedIOException;
022import java.nio.file.Path;
023import java.util.List;
024import java.util.Map;
025import javax.tools.JavaFileManager.Location;
026import javax.tools.StandardLocation;
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 * <p>While this interface may seem somewhat intimidating due to the number of methods it provides,
036 * you will usually only ever need to use a small subset of them. The main ones you probably will
037 * want to use are:
038 *
039 * <ul>
040 *   <li>
041 *     {@link #addSourcePathPackage(Path)} - for copying a source path tree from your file system.
042 *   </li>
043 *   <li>
044 *     {@link #createSourcePathPackage()} - for creating a new source path tree.
045 *   </li>
046 *   <li>
047 *    {@link #addClassPathPackage(Path)} - for adding a class path resource (usually a JAR or a
048 *    directory of packages of classes).
049 *   </li>
050 *   <li>
051 *    {@link #addModulePathModule(String, Path)} - for adding a module path resource (usually a JAR
052 *    or a directory of packages of classes).
053 *   </li>
054 *   <li>
055 *     {@link #getClassPathPackages()} - for fetching all class path resources.
056 *   </li>
057 *   <li>
058 *     {@link #getModulePathModules()} - for fetching all module path resources.
059 *   </li>
060 *   <li>
061 *     {@link #getSourceOutputPackages()} - for fetching all generated source packages.
062 *   </li>
063 *   <li>
064 *     {@link #getSourceOutputModules()} - for fetching all generated source modules.
065 *   </li>
066 *   <li>
067 *     {@link #close()} - to close any resources in the temporary file system.
068 *   </li>
069 * </ul>
070 *
071 * <p>A simple example of usage of this interface would be the following:
072 *
073 * <pre><code>
074 * try (Workspace workspace = Workspaces.newWorkspace()) {
075 *   workspace
076 *      .createSourcePathPackage()
077 *      .copyContentsFrom("src", "test", "resources", "test-data");
078 *
079 *   var compilation = someCompiler.compile(workspace);
080 *
081 *   assertThat(compilation).isSuccessful();
082 * }
083 * </code></pre>
084 *
085 * <p>As of 3.2.0, you can use a functional version of the above instead if this is more
086 * suitable for your use-case:
087 *
088 * <pre><code>
089 * Workspaces.newWorkspace().use(workspace -> {
090 *   ...
091 * });
092 * </code></pre>
093 *
094 * <p>Remember that files that are created as the result of a compilation can be queried via
095 * {@link JctFileManager}, which is accessible on the {@code compilation} result object. This may
096 * more accurately represent the logical project structure that is the result of various
097 * processing operations during compilation.
098 *
099 * @author Ashley Scopes
100 * @since 0.0.1
101 */
102@SuppressWarnings({"unused", "UnusedReturnValue"})
103public interface Workspace extends AutoCloseable {
104
105  /**
106   * Attempt to close all resources in this workspace.
107   *
108   * @throws UncheckedIOException if an error occurs.
109   */
110  @Override
111  void close();
112
113  /**
114   * Dump a visual representation of this workspace to the given appendable.
115   *
116   * <p>This provides a utility method for debugging.
117   *
118   * @param appendable the appendable to write to.
119   * @throws UncheckedIOException if an IO error occurs when writing to the appendable.
120   * @since 5.0.0
121   */
122  void dump(Appendable appendable);
123
124  /**
125   * Determine if the workspace is closed or not.
126   *
127   * @return {@code true} if closed, {@code false} if open.
128   * @since 0.4.0
129   */
130  boolean isClosed();
131
132  /**
133   * Get an immutable copy of the current paths to operate on.
134   *
135   * @return the paths.
136   */
137  Map<Location, List<? extends PathRoot>> getAllPaths();
138
139  /**
140   * Get the collection of path roots associated with the given module.
141   *
142   * <p>Usually this should only ever contain one path root at a maximum, although
143   * {@link Workspace} does not explicitly enforce this constraint.
144   *
145   * <p>If no results were found, then an empty collection is returned.
146   *
147   * @param location   the module-oriented or output location.
148   * @param moduleName the module name within the location.
149   * @return the collection of paths.
150   * @throws IllegalArgumentException if the location is neither
151   *                                  {@link Location#isModuleOrientedLocation() module-oriented} or
152   *                                  an {@link Location#isOutputLocation() output location}. This
153   *                                  will also be raised if this method is called with an instance
154   *                                  of {@link ModuleLocation} (you should use
155   *                                  {@link #getPackages(Location)} instead for this).
156   * @see #getPackages(Location)
157   * @see #getModules(Location)
158   * @since 0.1.0
159   */
160  List<? extends PathRoot> getModule(Location location, String moduleName);
161
162  /**
163   * Get the collection of modules associated with the given location.
164   *
165   * <p>If no results were found, then an empty map is returned.
166   *
167   * @param location the location to get the modules for.
168   * @return the map of module names to lists of associated paths.
169   * @throws IllegalArgumentException if the location is neither
170   *                                  {@link Location#isModuleOrientedLocation() module-oriented} or
171   *                                  an {@link Location#isOutputLocation() output location}. This
172   *                                  will also be raised if this method is called with an instance
173   *                                  of {@link ModuleLocation}.
174   * @see #getModule(Location, String)
175   * @since 0.1.0
176   */
177  Map<String, List<? extends PathRoot>> getModules(Location location);
178
179  /**
180   * Get the path strategy in use.
181   *
182   * @return the path strategy.
183   */
184  PathStrategy getPathStrategy();
185
186  /**
187   * Get the collection of path roots associated with the given location.
188   *
189   * <p>If no results were found, then an empty collection is returned.
190   *
191   * @param location the location to get.
192   * @return the collection of paths.
193   * @throws IllegalArgumentException if the location is
194   *                                  {@link Location#isModuleOrientedLocation() module-oriented}.
195   * @see #getModule(Location, String)
196   * @since 0.1.0
197   */
198  List<? extends PathRoot> getPackages(Location location);
199
200  /**
201   * Add an existing package root to this workspace and associate it with the given location.
202   *
203   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
204   * will be thrown:
205   *
206   * <ul>
207   *   <li>
208   *     The {@code location} must not be
209   *     {@link Location#isModuleOrientedLocation() module-oriented}.
210   *   </li>
211   *   <li>
212   *     The {@code path} must exist.
213   *   </li>
214   * </ul>
215   *
216   * <p>For example, this would add the package tree in {@code src/test/resources/packages}
217   * into the {@link StandardLocation#SOURCE_PATH source code path} that is used to compile files:
218   *
219   * <pre><code>
220   *   var path = Path.of("src", "test", "resources", "packages");
221   *   workspace.addPackage(StandardLocation.SOURCE_PATH, path);
222   * </code></pre>
223   *
224   * @param location the location to associate with.
225   * @param path     the path to add.
226   * @throws IllegalArgumentException if the inputs are invalid.
227   * @see #addModule(Location, String, Path)
228   * @see #createPackage(Location)
229   * @see #createModule(Location, String)
230   */
231  void addPackage(Location location, Path path);
232
233  /**
234   * Add an existing package root to this workspace and associate it with the given module name in
235   * the given location.
236   *
237   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
238   * will be thrown:
239   *
240   * <ul>
241   *   <li>
242   *     The {@code location} must be {@link Location#isModuleOrientedLocation() module-oriented}
243   *     or an {@link Location#isOutputLocation() output location}.
244   *   </li>
245   *   <li>
246   *     The {@code location} must not be a {@link ModuleLocation module-location handle} already.
247   *   </li>
248   *   <li>
249   *     The {@code path} must exist.
250   *   </li>
251   * </ul>
252   *
253   * <p>For example, this would add the package tree in {@code src/test/resources/packages}
254   * into the {@link StandardLocation#MODULE_SOURCE_PATH module source code path} under the module
255   * name {@code foo.bar}:
256   *
257   * <pre><code>
258   *   var path = Path.of("src", "test", "resources", "packages");
259   *   workspace.addModule(StandardLocation.MODULE_SOURCE_PATH, "foo.bar", path);
260   * </code></pre>
261   *
262   * @param location   the location to associate with.
263   * @param moduleName the name of the module.
264   * @param path       the path to add.
265   * @throws IllegalArgumentException if the inputs are invalid.
266   * @see #addPackage(Location, Path)
267   * @see #createPackage(Location)
268   * @see #createModule(Location, String)
269   */
270  void addModule(Location location, String moduleName, Path path);
271
272  /**
273   * Create a new test directory for a package root and associate it with the given location.
274   *
275   * <p>This path will be destroyed when the workspace is {@link #close() closed}.
276   *
277   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
278   * will be thrown:
279   *
280   * <ul>
281   *   <li>
282   *     The {@code location} must not be
283   *     {@link Location#isModuleOrientedLocation() module-oriented}.
284   *   </li>
285   * </ul>
286   *
287   * <p>For example, this would create a new source root that you could add files to:
288   *
289   * <pre><code>
290   *   workspace.createPackage(StandardLocation.SOURCE_PATH);
291   * </code></pre>
292   *
293   * @param location the location to associate with.
294   * @return the test directory that was created.
295   * @throws IllegalArgumentException if the inputs are invalid.
296   * @see #createModule(Location, String)
297   * @see #addPackage(Location, Path)
298   * @see #addModule(Location, String, Path)
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   * <p>For example, this would create a new multi-module source root that you could add files to,
322   * for a module named {@code foo.bar}:
323   *
324   * <pre><code>
325   *   workspace.createModule(StandardLocation.MODULE_SOURCE_PATH, "foo.bar");
326   * </code></pre>
327   *
328   * @param location   the location to associate with.
329   * @param moduleName the module name to use.
330   * @return the test directory that was created.
331   * @throws IllegalArgumentException if the inputs are invalid.
332   * @see #createPackage(Location)
333   * @see #addPackage(Location, Path)
334   * @see #addModule(Location, String, Path)
335   */
336  ManagedDirectory createModule(Location location, String moduleName);
337
338  /**
339   * Add a package to the {@link StandardLocation#CLASS_OUTPUT class outputs}.
340   *
341   * @param path the path to add.
342   * @throws IllegalArgumentException if the path does not exist.
343   * @see #addPackage(Location, Path)
344   * @see #addClassOutputModule(String, Path)
345   * @see #createClassOutputPackage()
346   * @see #createClassOutputModule(String)
347   */
348  default void addClassOutputPackage(Path path) {
349    addPackage(StandardLocation.CLASS_OUTPUT, path);
350  }
351
352  /**
353   * Add a module to the {@link StandardLocation#CLASS_OUTPUT class outputs}.
354   *
355   * @param moduleName the module name.
356   * @param path       the path to add.
357   * @throws IllegalArgumentException if the path does not exist.
358   * @see #addModule(Location, String, Path)
359   * @see #addClassOutputPackage(Path)
360   * @see #createClassOutputPackage()
361   * @see #createClassOutputModule(String)
362   */
363  default void addClassOutputModule(String moduleName, Path path) {
364    addModule(StandardLocation.CLASS_OUTPUT, moduleName, path);
365  }
366
367  /**
368   * Add a package to the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
369   *
370   * @param path the path to add.
371   * @throws IllegalArgumentException if the path does not exist.
372   * @see #addPackage(Location, Path)
373   * @see #addSourceOutputModule(String, Path)
374   * @see #createSourceOutputPackage()
375   * @see #createSourceOutputModule(String)
376   */
377  default void addSourceOutputPackage(Path path) {
378    addPackage(StandardLocation.SOURCE_OUTPUT, path);
379  }
380
381  /**
382   * Add a module to the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
383   *
384   * @param moduleName the module name.
385   * @param path       the path to add.
386   * @throws IllegalArgumentException if the path does not exist.
387   * @see #addModule(Location, String, Path)
388   * @see #addSourceOutputPackage(Path)
389   * @see #createSourceOutputPackage()
390   * @see #createSourceOutputModule(String)
391   */
392  default void addSourceOutputModule(String moduleName, Path path) {
393    addModule(StandardLocation.SOURCE_OUTPUT, moduleName, path);
394  }
395
396  /**
397   * Add a package to the {@link StandardLocation#CLASS_PATH class path}.
398   *
399   * <p>If you are adding JPMS modules, you may want to use
400   * {@link #addModulePathModule(String, Path)} instead.
401   *
402   * @param path the path to add.
403   * @throws IllegalArgumentException if the path does not exist.
404   * @see #addPackage(Location, Path)
405   * @see #addModulePathModule(String, Path)
406   * @see #createClassPathPackage()
407   * @see #createModulePathModule(String)
408   */
409  default void addClassPathPackage(Path path) {
410    addPackage(StandardLocation.CLASS_PATH, path);
411  }
412
413  /**
414   * Add a module to the {@link StandardLocation#MODULE_PATH module path}.
415   *
416   * <p>If you are adding non-JPMS modules, you may want to use
417   * {@link #addClassPathPackage(Path)} instead.
418   *
419   * @param moduleName the module name.
420   * @param path       the path to add.
421   * @throws IllegalArgumentException if the path does not exist.
422   * @see #addModule(Location, String, Path)
423   * @see #addClassPathPackage(Path)
424   * @see #createClassPathPackage()
425   * @see #createModulePathModule(String)
426   */
427  default void addModulePathModule(String moduleName, Path path) {
428    addModule(StandardLocation.MODULE_PATH, moduleName, path);
429  }
430
431  /**
432   * Add a package to the {@link StandardLocation#SOURCE_PATH legacy source path}.
433   *
434   * <p>This is the location you will usually tend to use for your source code.
435   *
436   * <p>If you wish to define multiple JPMS modules in your source code tree to compile together,
437   * you will want to consider using {@link #addSourcePathModule(String, Path)} instead. For most
438   * purposes, however, this method is the one you will want to be using if your code is not in a
439   * <strong>named</strong> module directory (so not something like
440   * {@code src/my.module/org/example/...}).
441   *
442   * @param path the path to add.
443   * @throws IllegalArgumentException if the path does not exist.
444   * @see #addPackage(Location, Path)
445   * @see #addSourcePathModule(String, Path)
446   * @see #createSourcePathPackage()
447   * @see #createSourcePathModule(String)
448   */
449  default void addSourcePathPackage(Path path) {
450    addPackage(StandardLocation.SOURCE_PATH, path);
451  }
452
453  /**
454   * Add a module to the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
455   *
456   * <p>Note that this will signal to the compiler to run in multi-module compilation mode.
457   * Any source packages that were {@link #addSourcePathPackage(Path) added} or
458   * {@link #createSourcePathPackage() created} will be ignored.
459   *
460   * <p>If you are using a single-module, you can
461   * {@link #addSourcePathPackage(Path) add a source package} and include a {@code module-info.java}
462   * in the base directory instead.
463   *
464   * @param moduleName the module name.
465   * @param path       the path to add.
466   * @throws IllegalArgumentException if the path does not exist.
467   * @see #addModule(Location, String, Path)
468   * @see #addSourcePathPackage(Path)
469   * @see #createSourcePathPackage()
470   * @see #createSourcePathModule(String)
471   */
472  default void addSourcePathModule(String moduleName, Path path) {
473    addModule(StandardLocation.MODULE_SOURCE_PATH, moduleName, path);
474  }
475
476  /**
477   * Add a package to the
478   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
479   *
480   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
481   * processor instances to run.
482   *
483   * @param path the path to add.
484   * @throws IllegalArgumentException if the path does not exist.
485   * @see #addPackage(Location, Path)
486   * @see #addAnnotationProcessorPathModule(String, Path)
487   * @see #createAnnotationProcessorPathPackage()
488   * @see #createAnnotationProcessorPathModule(String)
489   */
490  default void addAnnotationProcessorPathPackage(Path path) {
491    addPackage(StandardLocation.ANNOTATION_PROCESSOR_PATH, path);
492  }
493
494  /**
495   * Add a module to the
496   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
497   *
498   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
499   * processor instances to run.
500   *
501   * @param moduleName the module name.
502   * @param path       the path to add.
503   * @throws IllegalArgumentException if the path does not exist.
504   * @see #addModule(Location, String, Path)
505   * @see #addAnnotationProcessorPathPackage(Path)
506   * @see #createAnnotationProcessorPathPackage()
507   * @see #createAnnotationProcessorPathModule
508   */
509  default void addAnnotationProcessorPathModule(String moduleName, Path path) {
510    addModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName, path);
511  }
512
513  /**
514   * Create a package in the {@link StandardLocation#CLASS_OUTPUT class outputs}.
515   *
516   * @return the created test directory.
517   * @see #createPackage(Location)
518   * @see #createClassOutputModule(String)
519   * @see #addClassOutputPackage(Path)
520   * @see #addClassOutputModule(String, Path)
521   */
522  default ManagedDirectory createClassOutputPackage() {
523    return createPackage(StandardLocation.CLASS_OUTPUT);
524  }
525
526  /**
527   * Create a module in the {@link StandardLocation#CLASS_OUTPUT class outputs}.
528   *
529   * @param moduleName the module name.
530   * @return the created test directory.
531   * @see #createModule(Location, String)
532   * @see #createClassOutputPackage()
533   * @see #addClassOutputPackage(Path)
534   * @see #addClassOutputModule(String, Path)
535   */
536  default ManagedDirectory createClassOutputModule(String moduleName) {
537    return createModule(StandardLocation.CLASS_OUTPUT, moduleName);
538  }
539
540  /**
541   * Create a package in the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
542   *
543   * @return the created test directory.
544   * @see #createPackage(Location)
545   * @see #createSourceOutputModule(String)
546   * @see #addSourceOutputPackage(Path)
547   * @see #addSourceOutputModule(String, Path)
548   */
549  default ManagedDirectory createSourceOutputPackage() {
550    return createPackage(StandardLocation.SOURCE_OUTPUT);
551  }
552
553  /**
554   * Create a module in the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
555   *
556   * @param moduleName the module name.
557   * @return the created test directory.
558   * @see #createModule(Location, String)
559   * @see #createSourceOutputPackage()
560   * @see #addSourceOutputPackage(Path)
561   * @see #addSourceOutputModule(String, Path)
562   */
563  default ManagedDirectory createSourceOutputModule(String moduleName) {
564    return createModule(StandardLocation.SOURCE_OUTPUT, moduleName);
565  }
566
567  /**
568   * Create a package in the {@link StandardLocation#CLASS_PATH class path}.
569   *
570   * <p>If you are adding JPMS modules, you may want to use
571   * {@link #createModulePathModule(String)} instead.
572   *
573   * @return the created test directory.
574   * @see #createPackage(Location)
575   * @see #createModulePathModule(String)
576   * @see #addClassPathPackage(Path)
577   * @see #addModulePathModule(String, Path)
578   */
579  default ManagedDirectory createClassPathPackage() {
580    return createPackage(StandardLocation.CLASS_PATH);
581  }
582
583  /**
584   * Create a module in the {@link StandardLocation#MODULE_PATH module path}.
585   *
586   * <p>If you are adding non-JPMS modules, you may want to use
587   * {@link #createClassPathPackage()} instead.
588   *
589   * @param moduleName the module name.
590   * @return the created test directory.
591   * @see #createModule(Location, String)
592   * @see #createClassPathPackage()
593   * @see #addModulePathModule(String, Path)
594   * @see #addClassPathPackage(Path)
595   */
596  default ManagedDirectory createModulePathModule(String moduleName) {
597    return createModule(StandardLocation.MODULE_PATH, moduleName);
598  }
599
600  /**
601   * Create a package in the {@link StandardLocation#SOURCE_PATH source path}.
602   *
603   * <p>If you wish to define multiple JPMS modules in your source code tree to compile together,
604   * you will want to consider using {@link #createSourcePathModule(String)} instead. For most
605   * purposes, however, this method is the one you will want to be using if your code is not in a
606   * <strong>named</strong> module directory (so not something like
607   * {@code src/my.module/org/example/...}).
608   *
609   * @return the created test directory.
610   * @see #createPackage(Location)
611   * @see #createSourcePathModule(String)
612   * @see #addSourcePathPackage(Path)
613   * @see #addSourcePathModule(String, Path)
614   */
615  default ManagedDirectory createSourcePathPackage() {
616    return createPackage(StandardLocation.SOURCE_PATH);
617  }
618
619  /**
620   * Create a module in the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
621   *
622   * <p>Note that this will signal to the compiler to run in multi-module compilation mode.
623   * Any source packages that were {@link #createSourcePathPackage() created} or
624   * {@link #addSourcePathPackage(Path) added} will be ignored.
625   *
626   * <p>If you are using a single-module, you can
627   * {@link #createSourcePathPackage() create a source package} and include a
628   * {@code module-info.java} in the base directory instead.
629   *
630   * @param moduleName the module name.
631   * @return the created test directory.
632   * @see #createModule(Location, String)
633   * @see #createSourcePathPackage()
634   * @see #addSourcePathPackage(Path)
635   * @see #addSourcePathModule(String, Path)
636   */
637  default ManagedDirectory createSourcePathModule(String moduleName) {
638    return createModule(StandardLocation.MODULE_SOURCE_PATH, moduleName);
639  }
640
641  /**
642   * Create a package in the
643   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
644   *
645   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
646   * processor instances to run.
647   *
648   * @return the created test directory.
649   * @see #createPackage(Location)
650   * @see #createAnnotationProcessorPathModule(String)
651   * @see #addAnnotationProcessorPathPackage(Path)
652   * @see #addAnnotationProcessorPathModule(String, Path)
653   */
654  default ManagedDirectory createAnnotationProcessorPathPackage() {
655    return createPackage(StandardLocation.ANNOTATION_PROCESSOR_PATH);
656  }
657
658  /**
659   * Create a module in the
660   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
661   *
662   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
663   * processor instances to run.
664   *
665   * @param moduleName the module name.
666   * @return the created test directory.
667   * @see #createModule(Location, String)
668   * @see #createAnnotationProcessorPathPackage()
669   * @see #addAnnotationProcessorPathPackage(Path)
670   * @see #addAnnotationProcessorPathModule(String, Path)
671   */
672  default ManagedDirectory createAnnotationProcessorPathModule(String moduleName) {
673    return createModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName);
674  }
675
676  /**
677   * Get the non-module path roots for {@link StandardLocation#CLASS_OUTPUT class outputs}.
678   *
679   * @return the roots in a collection, or an empty collection if none were found.
680   * @since 0.1.0
681   */
682  default List<? extends PathRoot> getClassOutputPackages() {
683    return getPackages(StandardLocation.CLASS_OUTPUT);
684  }
685
686  /**
687   * Get the module path roots for {@link StandardLocation#CLASS_OUTPUT class outputs} for the given
688   * module name.
689   *
690   * @param moduleName the module name.
691   * @return the roots in a collection, or an empty collection if none were found.
692   * @since 0.1.0
693   */
694  default List<? extends PathRoot> getClassOutputModule(String moduleName) {
695    return getModule(StandardLocation.CLASS_OUTPUT, moduleName);
696  }
697
698  /**
699   * Get the module path roots for {@link StandardLocation#CLASS_OUTPUT class outputs}.
700   *
701   * @return the roots in a map of module names to lists of roots, or an empty map if none were
702   *     found.
703   * @since 0.1.0
704   */
705  default Map<String, List<? extends PathRoot>> getClassOutputModules() {
706    return getModules(StandardLocation.CLASS_OUTPUT);
707  }
708
709  /**
710   * Get the non-module path roots for {@link StandardLocation#SOURCE_OUTPUT source outputs}.
711   *
712   * @return the roots in a collection, or an empty collection if none were found.
713   * @since 0.1.0
714   */
715  default List<? extends PathRoot> getSourceOutputPackages() {
716    return getPackages(StandardLocation.SOURCE_OUTPUT);
717  }
718
719  /**
720   * Get the module path roots for {@link StandardLocation#SOURCE_OUTPUT source outputs} for the
721   * given module name.
722   *
723   * @param moduleName the module name.
724   * @return the roots in a collection, or an empty collection if none were found.
725   * @since 0.1.0
726   */
727  default List<? extends PathRoot> getSourceOutputModule(String moduleName) {
728    return getModule(StandardLocation.SOURCE_OUTPUT, moduleName);
729  }
730
731
732  /**
733   * Get the module path roots for {@link StandardLocation#SOURCE_OUTPUT source outputs}.
734   *
735   * @return the roots in a map of module names to lists of roots, or an empty map if none were
736   *     found.
737   * @since 0.1.0
738   */
739  default Map<String, List<? extends PathRoot>> getSourceOutputModules() {
740    return getModules(StandardLocation.SOURCE_OUTPUT);
741  }
742
743  /**
744   * Get the path roots for the {@link StandardLocation#CLASS_PATH class path}.
745   *
746   * @return the roots in a collection, or an empty collection if none were found.
747   * @since 0.1.0
748   */
749  default List<? extends PathRoot> getClassPathPackages() {
750    return getPackages(StandardLocation.CLASS_PATH);
751  }
752
753  /**
754   * Get the path roots for the {@link StandardLocation#SOURCE_PATH source path}.
755   *
756   * @return the roots in a collection, or an empty collection if none were found.
757   * @since 0.1.0
758   */
759  default List<? extends PathRoot> getSourcePathPackages() {
760    return getPackages(StandardLocation.SOURCE_PATH);
761  }
762
763  /**
764   * Get the path roots for the
765   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
766   *
767   * @return the roots in a collection, or an empty collection if none were found.
768   * @since 0.1.0
769   */
770  default List<? extends PathRoot> getAnnotationProcessorPathPackages() {
771    return getPackages(StandardLocation.ANNOTATION_PROCESSOR_PATH);
772  }
773
774  /**
775   * Get the path roots for the
776   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
777   *
778   * @param moduleName the module name to get.
779   * @return the roots in a collection, or an empty collection if none were found.
780   * @since 0.1.0
781   */
782  default List<? extends PathRoot> getAnnotationProcessorPathModule(String moduleName) {
783    return getModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName);
784  }
785
786  /**
787   * Get the module path roots for the
788   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
789   *
790   * @return the roots in a map of module names to lists of roots, or an empty map if none were
791   *     found.
792   * @since 0.1.0
793   */
794  default Map<String, List<? extends PathRoot>> getAnnotationProcessorPathModules() {
795    return getModules(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH);
796  }
797
798  /**
799   * Get the path roots for the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
800   *
801   * @param moduleName the module name to get.
802   * @return the roots in a collection, or an empty collection if none were found.
803   * @since 0.1.0
804   */
805  default List<? extends PathRoot> getSourcePathModule(String moduleName) {
806    return getModule(StandardLocation.MODULE_SOURCE_PATH, moduleName);
807  }
808
809  /**
810   * Get the module path roots for the
811   * {@link StandardLocation#MODULE_SOURCE_PATH module source paths}.
812   *
813   * @return the roots in a map of module names to lists of roots, or an empty map if none were
814   *     found.
815   * @since 0.1.0
816   */
817  default Map<String, List<? extends PathRoot>> getSourcePathModules() {
818    return getModules(StandardLocation.MODULE_SOURCE_PATH);
819  }
820
821  /**
822   * Get the path roots for the {@link StandardLocation#MODULE_PATH module path}.
823   *
824   * @param moduleName the module name to get.
825   * @return the roots in a collection, or an empty collection if none were found.
826   * @since 0.1.0
827   */
828  default List<? extends PathRoot> getModulePathModule(String moduleName) {
829    return getModule(StandardLocation.MODULE_PATH, moduleName);
830  }
831
832  /**
833   * Get the module path roots for the {@link StandardLocation#MODULE_PATH module paths}.
834   *
835   * @return the roots in a map of module names to lists of roots, or an empty map if none were
836   *     found.
837   * @since 0.1.0
838   */
839  default Map<String, List<? extends PathRoot>> getModulePathModules() {
840    return getModules(StandardLocation.MODULE_PATH);
841  }
842
843  /**
844   * Functional equivalent of consuming this object with a try-with-resources.
845   *
846   * <p>This workspace will be {@link #close() closed} upon completion of this method or upon
847   * an exception being raised.
848   *
849   * @param consumer the consumer to pass this object to.
850   * @param <T> the exception that the consumer can throw, or {@link RuntimeException}
851   *            if no checked exception is thrown.
852   * @throws UncheckedIOException if the closure fails after the consumer is called.
853   * @throws T the checked exception that the consumer can throw.
854   * @since 3.2.0
855   */
856  default <T extends Throwable> void use(ThrowingWorkspaceConsumer<T> consumer) throws T {
857    try {
858      consumer.accept(this);
859    } finally {
860      close();
861    }
862  }
863
864  /**
865   * A consumer functional interface that consumes a workspace and
866   * can throw a checked exception.
867   *
868   * @param <T> the exception type that can be thrown, or {@link RuntimeException}
869   *            if no checked exception is thrown.
870   * @author Ashley Scopes
871   * @since 3.2.0
872   */
873  interface ThrowingWorkspaceConsumer<T extends Throwable> {
874
875    /**
876     * Consume a workspace.
877     *
878     * @param workspace the workspace.
879     * @throws T the checked exception that can be thrown.
880     */
881    void accept(Workspace workspace) throws T;
882  }
883}
884