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