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