001/*
002 * Copyright (C) 2022 - 2024, the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.github.ascopes.jct.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   * Determine if the workspace is closed or not.
114   *
115   * @return {@code true} if closed, {@code false} if open.
116   * @since 0.4.0
117   */
118  boolean isClosed();
119
120  /**
121   * Get an immutable copy of the current paths to operate on.
122   *
123   * @return the paths.
124   */
125  Map<Location, List<? extends PathRoot>> getAllPaths();
126
127  /**
128   * Get the collection of path roots associated with the given module.
129   *
130   * <p>Usually this should only ever contain one path root at a maximum, although
131   * {@link Workspace} does not explicitly enforce this constraint.
132   *
133   * <p>If no results were found, then an empty collection is returned.
134   *
135   * @param location   the module-oriented or output location.
136   * @param moduleName the module name within the location.
137   * @return the collection of paths.
138   * @throws IllegalArgumentException if the location is neither
139   *                                  {@link Location#isModuleOrientedLocation() module-oriented} or
140   *                                  an {@link Location#isOutputLocation() output location}. This
141   *                                  will also be raised if this method is called with an instance
142   *                                  of {@link ModuleLocation} (you should use
143   *                                  {@link #getPackages(Location)} instead for this).
144   * @see #getPackages(Location)
145   * @see #getModules(Location)
146   * @since 0.1.0
147   */
148  List<? extends PathRoot> getModule(Location location, String moduleName);
149
150  /**
151   * Get the collection of modules associated with the given location.
152   *
153   * <p>If no results were found, then an empty map is returned.
154   *
155   * @param location the location to get the modules for.
156   * @return the map of module names to lists of associated paths.
157   * @throws IllegalArgumentException if the location is neither
158   *                                  {@link Location#isModuleOrientedLocation() module-oriented} or
159   *                                  an {@link Location#isOutputLocation() output location}. This
160   *                                  will also be raised if this method is called with an instance
161   *                                  of {@link ModuleLocation}.
162   * @see #getModule(Location, String)
163   * @since 0.1.0
164   */
165  Map<String, List<? extends PathRoot>> getModules(Location location);
166
167  /**
168   * Get the path strategy in use.
169   *
170   * @return the path strategy.
171   */
172  PathStrategy getPathStrategy();
173
174  /**
175   * Get the collection of path roots associated with the given location.
176   *
177   * <p>If no results were found, then an empty collection is returned.
178   *
179   * @param location the location to get.
180   * @return the collection of paths.
181   * @throws IllegalArgumentException if the location is
182   *                                  {@link Location#isModuleOrientedLocation() module-oriented}.
183   * @see #getModule(Location, String)
184   * @since 0.1.0
185   */
186  List<? extends PathRoot> getPackages(Location location);
187
188  /**
189   * Add an existing package root to this workspace and associate it with the given location.
190   *
191   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
192   * will be thrown:
193   *
194   * <ul>
195   *   <li>
196   *     The {@code location} must not be
197   *     {@link Location#isModuleOrientedLocation() module-oriented}.
198   *   </li>
199   *   <li>
200   *     The {@code path} must exist.
201   *   </li>
202   * </ul>
203   *
204   * <p>For example, this would add the package tree in {@code src/test/resources/packages}
205   * into the {@link StandardLocation#SOURCE_PATH source code path} that is used to compile files:
206   *
207   * <pre><code>
208   *   var path = Path.of("src", "test", "resources", "packages");
209   *   workspace.addPackage(StandardLocation.SOURCE_PATH, path);
210   * </code></pre>
211   *
212   * @param location the location to associate with.
213   * @param path     the path to add.
214   * @throws IllegalArgumentException if the inputs are invalid.
215   * @see #addModule(Location, String, Path)
216   * @see #createPackage(Location)
217   * @see #createModule(Location, String)
218   */
219  void addPackage(Location location, Path path);
220
221  /**
222   * Add an existing package root to this workspace and associate it with the given module name in
223   * the given location.
224   *
225   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
226   * will be thrown:
227   *
228   * <ul>
229   *   <li>
230   *     The {@code location} must be {@link Location#isModuleOrientedLocation() module-oriented}
231   *     or an {@link Location#isOutputLocation() output location}.
232   *   </li>
233   *   <li>
234   *     The {@code location} must not be a {@link ModuleLocation module-location handle} already.
235   *   </li>
236   *   <li>
237   *     The {@code path} must exist.
238   *   </li>
239   * </ul>
240   *
241   * <p>For example, this would add the package tree in {@code src/test/resources/packages}
242   * into the {@link StandardLocation#MODULE_SOURCE_PATH module source code path} under the module
243   * name {@code foo.bar}:
244   *
245   * <pre><code>
246   *   var path = Path.of("src", "test", "resources", "packages");
247   *   workspace.addModule(StandardLocation.MODULE_SOURCE_PATH, "foo.bar", path);
248   * </code></pre>
249   *
250   * @param location   the location to associate with.
251   * @param moduleName the name of the module.
252   * @param path       the path to add.
253   * @throws IllegalArgumentException if the inputs are invalid.
254   * @see #addPackage(Location, Path)
255   * @see #createPackage(Location)
256   * @see #createModule(Location, String)
257   */
258  void addModule(Location location, String moduleName, Path path);
259
260  /**
261   * Create a new test directory for a package root and associate it with the given location.
262   *
263   * <p>This path will be destroyed when the workspace is {@link #close() closed}.
264   *
265   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
266   * will be thrown:
267   *
268   * <ul>
269   *   <li>
270   *     The {@code location} must not be
271   *     {@link Location#isModuleOrientedLocation() module-oriented}.
272   *   </li>
273   * </ul>
274   *
275   * <p>For example, this would create a new source root that you could add files to:
276   *
277   * <pre><code>
278   *   workspace.createPackage(StandardLocation.SOURCE_PATH);
279   * </code></pre>
280   *
281   * @param location the location to associate with.
282   * @return the test directory that was created.
283   * @throws IllegalArgumentException if the inputs are invalid.
284   * @see #createModule(Location, String)
285   * @see #addPackage(Location, Path)
286   * @see #addModule(Location, String, Path)
287   */
288  ManagedDirectory createPackage(Location location);
289
290  /**
291   * Create a new test directory for a package root and associate it with the given module name in
292   * the given location.
293   *
294   * <p>This path will be destroyed when the workspace is {@link #close() closed}.
295   *
296   * <p>The following constraints must be met, otherwise an {@link IllegalArgumentException}
297   * will be thrown:
298   *
299   * <ul>
300   *   <li>
301   *     The {@code location} must be {@link Location#isModuleOrientedLocation() module-oriented}
302   *     or an {@link Location#isOutputLocation() output location}.
303   *   </li>
304   *   <li>
305   *     The {@code location} must not be a {@link ModuleLocation module-location handle} already.
306   *   </li>
307   * </ul>
308   *
309   * <p>For example, this would create a new multi-module source root that you could add files to,
310   * for a module named {@code foo.bar}:
311   *
312   * <pre><code>
313   *   workspace.createModule(StandardLocation.MODULE_SOURCE_PATH, "foo.bar");
314   * </code></pre>
315   *
316   * @param location   the location to associate with.
317   * @param moduleName the module name to use.
318   * @return the test directory that was created.
319   * @throws IllegalArgumentException if the inputs are invalid.
320   * @see #createPackage(Location)
321   * @see #addPackage(Location, Path)
322   * @see #addModule(Location, String, Path)
323   */
324  ManagedDirectory createModule(Location location, String moduleName);
325
326  /**
327   * Add a package to the {@link StandardLocation#CLASS_OUTPUT class outputs}.
328   *
329   * @param path the path to add.
330   * @throws IllegalArgumentException if the path does not exist.
331   * @see #addPackage(Location, Path)
332   * @see #addClassOutputModule(String, Path)
333   * @see #createClassOutputPackage()
334   * @see #createClassOutputModule(String)
335   */
336  default void addClassOutputPackage(Path path) {
337    addPackage(StandardLocation.CLASS_OUTPUT, path);
338  }
339
340  /**
341   * Add a module to the {@link StandardLocation#CLASS_OUTPUT class outputs}.
342   *
343   * @param moduleName the module name.
344   * @param path       the path to add.
345   * @throws IllegalArgumentException if the path does not exist.
346   * @see #addModule(Location, String, Path)
347   * @see #addClassOutputPackage(Path)
348   * @see #createClassOutputPackage()
349   * @see #createClassOutputModule(String)
350   */
351  default void addClassOutputModule(String moduleName, Path path) {
352    addModule(StandardLocation.CLASS_OUTPUT, moduleName, path);
353  }
354
355  /**
356   * Add a package to the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
357   *
358   * @param path the path to add.
359   * @throws IllegalArgumentException if the path does not exist.
360   * @see #addPackage(Location, Path)
361   * @see #addSourceOutputModule(String, Path)
362   * @see #createSourceOutputPackage()
363   * @see #createSourceOutputModule(String)
364   */
365  default void addSourceOutputPackage(Path path) {
366    addPackage(StandardLocation.SOURCE_OUTPUT, path);
367  }
368
369  /**
370   * Add a module to the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
371   *
372   * @param moduleName the module name.
373   * @param path       the path to add.
374   * @throws IllegalArgumentException if the path does not exist.
375   * @see #addModule(Location, String, Path)
376   * @see #addSourceOutputPackage(Path)
377   * @see #createSourceOutputPackage()
378   * @see #createSourceOutputModule(String)
379   */
380  default void addSourceOutputModule(String moduleName, Path path) {
381    addModule(StandardLocation.SOURCE_OUTPUT, moduleName, path);
382  }
383
384  /**
385   * Add a package to the {@link StandardLocation#CLASS_PATH class path}.
386   *
387   * <p>If you are adding JPMS modules, you may want to use
388   * {@link #addModulePathModule(String, Path)} instead.
389   *
390   * @param path the path to add.
391   * @throws IllegalArgumentException if the path does not exist.
392   * @see #addPackage(Location, Path)
393   * @see #addModulePathModule(String, Path)
394   * @see #createClassPathPackage()
395   * @see #createModulePathModule(String)
396   */
397  default void addClassPathPackage(Path path) {
398    addPackage(StandardLocation.CLASS_PATH, path);
399  }
400
401  /**
402   * Add a module to the {@link StandardLocation#MODULE_PATH module path}.
403   *
404   * <p>If you are adding non-JPMS modules, you may want to use
405   * {@link #addClassPathPackage(Path)} instead.
406   *
407   * @param moduleName the module name.
408   * @param path       the path to add.
409   * @throws IllegalArgumentException if the path does not exist.
410   * @see #addModule(Location, String, Path)
411   * @see #addClassPathPackage(Path)
412   * @see #createClassPathPackage()
413   * @see #createModulePathModule(String)
414   */
415  default void addModulePathModule(String moduleName, Path path) {
416    addModule(StandardLocation.MODULE_PATH, moduleName, path);
417  }
418
419  /**
420   * Add a package to the {@link StandardLocation#SOURCE_PATH legacy source path}.
421   *
422   * <p>This is the location you will usually tend to use for your source code.
423   *
424   * <p>If you wish to define multiple JPMS modules in your source code tree to compile together,
425   * you will want to consider using {@link #addSourcePathModule(String, Path)} instead. For most
426   * purposes, however, this method is the one you will want to be using if your code is not in a
427   * <strong>named</strong> module directory (so not something like
428   * {@code src/my.module/org/example/...}).
429   *
430   * @param path the path to add.
431   * @throws IllegalArgumentException if the path does not exist.
432   * @see #addPackage(Location, Path)
433   * @see #addSourcePathModule(String, Path)
434   * @see #createSourcePathPackage()
435   * @see #createSourcePathModule(String)
436   */
437  default void addSourcePathPackage(Path path) {
438    addPackage(StandardLocation.SOURCE_PATH, path);
439  }
440
441  /**
442   * Add a module to the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
443   *
444   * <p>Note that this will signal to the compiler to run in multi-module compilation mode.
445   * Any source packages that were {@link #addSourcePathPackage(Path) added} or
446   * {@link #createSourcePathPackage() created} will be ignored.
447   *
448   * <p>If you are using a single-module, you can
449   * {@link #addSourcePathPackage(Path) add a source package} and include a {@code module-info.java}
450   * in the base directory instead.
451   *
452   * @param moduleName the module name.
453   * @param path       the path to add.
454   * @throws IllegalArgumentException if the path does not exist.
455   * @see #addModule(Location, String, Path)
456   * @see #addSourcePathPackage(Path)
457   * @see #createSourcePathPackage()
458   * @see #createSourcePathModule(String)
459   */
460  default void addSourcePathModule(String moduleName, Path path) {
461    addModule(StandardLocation.MODULE_SOURCE_PATH, moduleName, path);
462  }
463
464  /**
465   * Add a package to the
466   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
467   *
468   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
469   * processor instances to run.
470   *
471   * @param path the path to add.
472   * @throws IllegalArgumentException if the path does not exist.
473   * @see #addPackage(Location, Path)
474   * @see #addAnnotationProcessorPathModule(String, Path)
475   * @see #createAnnotationProcessorPathPackage()
476   * @see #createAnnotationProcessorPathModule(String)
477   */
478  default void addAnnotationProcessorPathPackage(Path path) {
479    addPackage(StandardLocation.ANNOTATION_PROCESSOR_PATH, path);
480  }
481
482  /**
483   * Add a module to the
484   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
485   *
486   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
487   * processor instances to run.
488   *
489   * @param moduleName the module name.
490   * @param path       the path to add.
491   * @throws IllegalArgumentException if the path does not exist.
492   * @see #addModule(Location, String, Path)
493   * @see #addAnnotationProcessorPathPackage(Path)
494   * @see #createAnnotationProcessorPathPackage()
495   * @see #createAnnotationProcessorPathModule
496   */
497  default void addAnnotationProcessorPathModule(String moduleName, Path path) {
498    addModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName, path);
499  }
500
501  /**
502   * Create a package in the {@link StandardLocation#CLASS_OUTPUT class outputs}.
503   *
504   * @return the created test directory.
505   * @see #createPackage(Location)
506   * @see #createClassOutputModule(String)
507   * @see #addClassOutputPackage(Path)
508   * @see #addClassOutputModule(String, Path)
509   */
510  default ManagedDirectory createClassOutputPackage() {
511    return createPackage(StandardLocation.CLASS_OUTPUT);
512  }
513
514  /**
515   * Create a module in the {@link StandardLocation#CLASS_OUTPUT class outputs}.
516   *
517   * @param moduleName the module name.
518   * @return the created test directory.
519   * @see #createModule(Location, String)
520   * @see #createClassOutputPackage()
521   * @see #addClassOutputPackage(Path)
522   * @see #addClassOutputModule(String, Path)
523   */
524  default ManagedDirectory createClassOutputModule(String moduleName) {
525    return createModule(StandardLocation.CLASS_OUTPUT, moduleName);
526  }
527
528  /**
529   * Create a package in the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
530   *
531   * @return the created test directory.
532   * @see #createPackage(Location)
533   * @see #createSourceOutputModule(String)
534   * @see #addSourceOutputPackage(Path)
535   * @see #addSourceOutputModule(String, Path)
536   */
537  default ManagedDirectory createSourceOutputPackage() {
538    return createPackage(StandardLocation.SOURCE_OUTPUT);
539  }
540
541  /**
542   * Create a module in the {@link StandardLocation#SOURCE_OUTPUT generated source outputs}.
543   *
544   * @param moduleName the module name.
545   * @return the created test directory.
546   * @see #createModule(Location, String)
547   * @see #createSourceOutputPackage()
548   * @see #addSourceOutputPackage(Path)
549   * @see #addSourceOutputModule(String, Path)
550   */
551  default ManagedDirectory createSourceOutputModule(String moduleName) {
552    return createModule(StandardLocation.SOURCE_OUTPUT, moduleName);
553  }
554
555  /**
556   * Create a package in the {@link StandardLocation#CLASS_PATH class path}.
557   *
558   * <p>If you are adding JPMS modules, you may want to use
559   * {@link #createModulePathModule(String)} instead.
560   *
561   * @return the created test directory.
562   * @see #createPackage(Location)
563   * @see #createModulePathModule(String)
564   * @see #addClassPathPackage(Path)
565   * @see #addModulePathModule(String, Path)
566   */
567  default ManagedDirectory createClassPathPackage() {
568    return createPackage(StandardLocation.CLASS_PATH);
569  }
570
571  /**
572   * Create a module in the {@link StandardLocation#MODULE_PATH module path}.
573   *
574   * <p>If you are adding non-JPMS modules, you may want to use
575   * {@link #createClassPathPackage()} instead.
576   *
577   * @param moduleName the module name.
578   * @return the created test directory.
579   * @see #createModule(Location, String)
580   * @see #createClassPathPackage()
581   * @see #addModulePathModule(String, Path)
582   * @see #addClassPathPackage(Path)
583   */
584  default ManagedDirectory createModulePathModule(String moduleName) {
585    return createModule(StandardLocation.MODULE_PATH, moduleName);
586  }
587
588  /**
589   * Create a package in the {@link StandardLocation#SOURCE_PATH source path}.
590   *
591   * <p>If you wish to define multiple JPMS modules in your source code tree to compile together,
592   * you will want to consider using {@link #createSourcePathModule(String)} instead. For most
593   * purposes, however, this method is the one you will want to be using if your code is not in a
594   * <strong>named</strong> module directory (so not something like
595   * {@code src/my.module/org/example/...}).
596   *
597   * @return the created test directory.
598   * @see #createPackage(Location)
599   * @see #createSourcePathModule(String)
600   * @see #addSourcePathPackage(Path)
601   * @see #addSourcePathModule(String, Path)
602   */
603  default ManagedDirectory createSourcePathPackage() {
604    return createPackage(StandardLocation.SOURCE_PATH);
605  }
606
607  /**
608   * Create a module in the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
609   *
610   * <p>Note that this will signal to the compiler to run in multi-module compilation mode.
611   * Any source packages that were {@link #createSourcePathPackage() created} or
612   * {@link #addSourcePathPackage(Path) added} will be ignored.
613   *
614   * <p>If you are using a single-module, you can
615   * {@link #createSourcePathPackage() create a source package} and include a
616   * {@code module-info.java} in the base directory instead.
617   *
618   * @param moduleName the module name.
619   * @return the created test directory.
620   * @see #createModule(Location, String)
621   * @see #createSourcePathPackage()
622   * @see #addSourcePathPackage(Path)
623   * @see #addSourcePathModule(String, Path)
624   */
625  default ManagedDirectory createSourcePathModule(String moduleName) {
626    return createModule(StandardLocation.MODULE_SOURCE_PATH, moduleName);
627  }
628
629  /**
630   * Create a package in the
631   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
632   *
633   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
634   * processor instances to run.
635   *
636   * @return the created test directory.
637   * @see #createPackage(Location)
638   * @see #createAnnotationProcessorPathModule(String)
639   * @see #addAnnotationProcessorPathPackage(Path)
640   * @see #addAnnotationProcessorPathModule(String, Path)
641   */
642  default ManagedDirectory createAnnotationProcessorPathPackage() {
643    return createPackage(StandardLocation.ANNOTATION_PROCESSOR_PATH);
644  }
645
646  /**
647   * Create a module in the
648   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
649   *
650   * <p>Note that this will be ignored if the compiler is provided with explicit annotation
651   * processor instances to run.
652   *
653   * @param moduleName the module name.
654   * @return the created test directory.
655   * @see #createModule(Location, String)
656   * @see #createAnnotationProcessorPathPackage()
657   * @see #addAnnotationProcessorPathPackage(Path)
658   * @see #addAnnotationProcessorPathModule(String, Path)
659   */
660  default ManagedDirectory createAnnotationProcessorPathModule(String moduleName) {
661    return createModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName);
662  }
663
664  /**
665   * Get the non-module path roots for {@link StandardLocation#CLASS_OUTPUT class outputs}.
666   *
667   * @return the roots in a collection, or an empty collection if none were found.
668   * @since 0.1.0
669   */
670  default List<? extends PathRoot> getClassOutputPackages() {
671    return getPackages(StandardLocation.CLASS_OUTPUT);
672  }
673
674  /**
675   * Get the module path roots for {@link StandardLocation#CLASS_OUTPUT class outputs} for the given
676   * module name.
677   *
678   * @param moduleName the module name.
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> getClassOutputModule(String moduleName) {
683    return getModule(StandardLocation.CLASS_OUTPUT, moduleName);
684  }
685
686  /**
687   * Get the module path roots for {@link StandardLocation#CLASS_OUTPUT class outputs}.
688   *
689   * @return the roots in a map of module names to lists of roots, or an empty map if none were
690   *     found.
691   * @since 0.1.0
692   */
693  default Map<String, List<? extends PathRoot>> getClassOutputModules() {
694    return getModules(StandardLocation.CLASS_OUTPUT);
695  }
696
697  /**
698   * Get the non-module path roots for {@link StandardLocation#SOURCE_OUTPUT source outputs}.
699   *
700   * @return the roots in a collection, or an empty collection if none were found.
701   * @since 0.1.0
702   */
703  default List<? extends PathRoot> getSourceOutputPackages() {
704    return getPackages(StandardLocation.SOURCE_OUTPUT);
705  }
706
707  /**
708   * Get the module path roots for {@link StandardLocation#SOURCE_OUTPUT source outputs} for the
709   * given module name.
710   *
711   * @param moduleName the module name.
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> getSourceOutputModule(String moduleName) {
716    return getModule(StandardLocation.SOURCE_OUTPUT, moduleName);
717  }
718
719
720  /**
721   * Get the module path roots for {@link StandardLocation#SOURCE_OUTPUT source outputs}.
722   *
723   * @return the roots in a map of module names to lists of roots, or an empty map if none were
724   *     found.
725   * @since 0.1.0
726   */
727  default Map<String, List<? extends PathRoot>> getSourceOutputModules() {
728    return getModules(StandardLocation.SOURCE_OUTPUT);
729  }
730
731  /**
732   * Get the path roots for the {@link StandardLocation#CLASS_PATH class path}.
733   *
734   * @return the roots in a collection, or an empty collection if none were found.
735   * @since 0.1.0
736   */
737  default List<? extends PathRoot> getClassPathPackages() {
738    return getPackages(StandardLocation.CLASS_PATH);
739  }
740
741  /**
742   * Get the path roots for the {@link StandardLocation#SOURCE_PATH source path}.
743   *
744   * @return the roots in a collection, or an empty collection if none were found.
745   * @since 0.1.0
746   */
747  default List<? extends PathRoot> getSourcePathPackages() {
748    return getPackages(StandardLocation.SOURCE_PATH);
749  }
750
751  /**
752   * Get the path roots for the
753   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor 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> getAnnotationProcessorPathPackages() {
759    return getPackages(StandardLocation.ANNOTATION_PROCESSOR_PATH);
760  }
761
762  /**
763   * Get the path roots for the
764   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
765   *
766   * @param moduleName the module name to get.
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> getAnnotationProcessorPathModule(String moduleName) {
771    return getModule(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, moduleName);
772  }
773
774  /**
775   * Get the module path roots for the
776   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
777   *
778   * @return the roots in a map of module names to lists of roots, or an empty map if none were
779   *     found.
780   * @since 0.1.0
781   */
782  default Map<String, List<? extends PathRoot>> getAnnotationProcessorPathModules() {
783    return getModules(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH);
784  }
785
786  /**
787   * Get the path roots for the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
788   *
789   * @param moduleName the module name to get.
790   * @return the roots in a collection, or an empty collection if none were found.
791   * @since 0.1.0
792   */
793  default List<? extends PathRoot> getSourcePathModule(String moduleName) {
794    return getModule(StandardLocation.MODULE_SOURCE_PATH, moduleName);
795  }
796
797  /**
798   * Get the module path roots for the
799   * {@link StandardLocation#MODULE_SOURCE_PATH module source paths}.
800   *
801   * @return the roots in a map of module names to lists of roots, or an empty map if none were
802   *     found.
803   * @since 0.1.0
804   */
805  default Map<String, List<? extends PathRoot>> getSourcePathModules() {
806    return getModules(StandardLocation.MODULE_SOURCE_PATH);
807  }
808
809  /**
810   * Get the path roots for the {@link StandardLocation#MODULE_PATH module path}.
811   *
812   * @param moduleName the module name to get.
813   * @return the roots in a collection, or an empty collection if none were found.
814   * @since 0.1.0
815   */
816  default List<? extends PathRoot> getModulePathModule(String moduleName) {
817    return getModule(StandardLocation.MODULE_PATH, moduleName);
818  }
819
820  /**
821   * Get the module path roots for the {@link StandardLocation#MODULE_PATH module paths}.
822   *
823   * @return the roots in a map of module names to lists of roots, or an empty map if none were
824   *     found.
825   * @since 0.1.0
826   */
827  default Map<String, List<? extends PathRoot>> getModulePathModules() {
828    return getModules(StandardLocation.MODULE_PATH);
829  }
830
831  /**
832   * Functional equivalent of consuming this object with a try-with-resources.
833   *
834   * <p>This workspace will be {@link #close() closed} upon completion of this method or upon
835   * an exception being raised.
836   *
837   * @param consumer the consumer to pass this object to.
838   * @param <T> the exception that the consumer can throw, or {@link RuntimeException}
839   *            if no checked exception is thrown.
840   * @throws UncheckedIOException if the closure fails after the consumer is called.
841   * @throws T the checked exception that the consumer can throw.
842   * @since 3.2.0
843   */
844  default <T extends Throwable> void use(ThrowingWorkspaceConsumer<T> consumer) throws T {
845    try {
846      consumer.accept(this);
847    } finally {
848      close();
849    }
850  }
851
852  /**
853   * A consumer functional interface that consumes a workspace and
854   * can throw a checked exception.
855   *
856   * @param <T> the exception type that can be thrown, or {@link RuntimeException}
857   *            if no checked exception is thrown.
858   * @author Ashley Scopes
859   * @since 3.2.0
860   */
861  interface ThrowingWorkspaceConsumer<T extends Throwable> {
862
863    /**
864     * Consume a workspace.
865     *
866     * @param workspace the workspace.
867     * @throws T the checked exception that can be thrown.
868     */
869    void accept(Workspace workspace) throws T;
870  }
871}
872