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.filemanagers;
017
018import io.github.ascopes.jct.containers.ModuleContainerGroup;
019import io.github.ascopes.jct.containers.OutputContainerGroup;
020import io.github.ascopes.jct.containers.PackageContainerGroup;
021import io.github.ascopes.jct.workspaces.PathRoot;
022import io.github.ascopes.jct.workspaces.Workspace;
023import java.io.IOException;
024import java.util.Collection;
025import java.util.Iterator;
026import java.util.Set;
027import javax.tools.FileObject;
028import javax.tools.JavaFileManager;
029import javax.tools.JavaFileObject;
030import javax.tools.JavaFileObject.Kind;
031import javax.tools.StandardLocation;
032import org.apiguardian.api.API;
033import org.apiguardian.api.API.Status;
034import org.jspecify.annotations.Nullable;
035
036/**
037 * Extension around a {@link JavaFileManager} that allows adding of {@link PathRoot} objects to the
038 * manager.
039 *
040 * <p>This component is responsible for bridging the gap between a {@link Workspace} and
041 * a {@link javax.tools.JavaCompiler} when performing a compilation, and thus includes a number of
042 * required operations that the compiler will query the file system with. In addition, this
043 * interface also defines a number of additional functionalities that are useful for querying and
044 * verifying the outcome of compilations within tests.
045 *
046 * @author Ashley Scopes
047 * @since 0.0.1
048 */
049@API(since = "0.0.1", status = Status.STABLE)
050public interface JctFileManager extends JavaFileManager {
051
052  /**
053   * Add a package-oriented path to a given location.
054   *
055   * <p>To add a module, first obtain the module location using
056   * {@link #getLocationForModule(Location, String)}, and pass that result to this call.
057   *
058   * @param location the location to use.
059   * @param path     the path to add.
060   * @see #getLocationForModule(Location, String)
061   */
062  void addPath(Location location, PathRoot path);
063
064  /**
065   * Add a collection of package-oriented paths to a given location.
066   *
067   * <p>To add a module, first obtain the module location using
068   * {@link #getLocationForModule(Location, String)}, and pass that result to this call.
069   *
070   * @param location the location to use.
071   * @param paths    the paths to add.
072   * @see #getLocationForModule(Location, String)
073   */
074  void addPaths(Location location, Collection<? extends PathRoot> paths);
075
076  /**
077   * Close this file manager.
078   *
079   * <p>If the file manager is already closed, this will have no effect.
080   *
081   * <p>JCT File managers may choose to not implement this operation if not applicable.
082   *
083   * @throws IOException if an IO error occurs during closure.
084   */
085  @Override
086  void close() throws IOException;
087
088  /**
089   * Determine if the given file object resides in the given location.
090   *
091   * <p>If package-oriented, then the file must exist in the given location directly.
092   *
093   * <p>If module-oriented, then the file must exist in one of the modules within the location.
094   * This implies that {@link #getLocationForModule(Location, JavaFileObject)} would also return
095   * a valid non-null value.
096   *
097   * @param location the location.
098   * @param fileObject the file object.
099   * @return {@code true} if the file exists in the given location, or {@code false} otherwise.
100   * @throws IOException if an IO error occurs during the lookup.
101   */
102  @Override
103  boolean contains(Location location, FileObject fileObject) throws IOException;
104
105  /**
106   * Copy all containers from the first location to the second location.
107   *
108   * @param from the first location.
109   * @param to   the second location.
110   */
111  void copyContainers(Location from, Location to);
112
113  /**
114   * Register an empty container for the given location to indicate to the compiler that the feature
115   * exists, but has no configured paths.
116   *
117   * <p>This is needed to coerce the behaviour for annotation processing in some cases.
118   *
119   * <p>If the location already exists, then do not do anything.
120   *
121   * <p>If the location is an output location, then this operation does not make any sense, since
122   * an empty location cannot have files output to it. In this case, you will likely get an
123   * exception.
124   *
125   * <p>Likewise, this operation does not make sense for module locations within a module-oriented
126   * location group, so this operation will fail with an error for those inputs as well.
127   *
128   * @param location the location to apply an empty container for.
129   * @throws IllegalArgumentException if the location is an output location or a module location.
130   */
131  void createEmptyLocation(Location location);
132
133  /**
134   * Flush this file manager.
135   *
136   * <p>If the file manager is already closed, this will have no effect.
137   *
138   * <p>JCT File managers may choose to not implement this operation if not applicable.
139   *
140   * @throws IOException if an IO error occurs during flushing.
141   */
142  @Override
143  void flush() throws IOException;
144
145  /**
146   * Get the class loader for loading classes for the given location.
147   *
148   * @param location the location to fetch a class loader for.
149   * @return a class loader, or {@code null} if loading classes for the given location is not
150   *     supported.
151   * @throws SecurityException        if a class loader can not be created in the current security
152   *                                  context
153   * @throws IllegalStateException    if {@link #close} has been called and this file manager cannot
154   *                                  be reopened
155   * @throws IllegalArgumentException if the location is a module-oriented location
156   */
157  @Nullable
158  @Override
159  ClassLoader getClassLoader(Location location);
160
161  /**
162   * Get the associated effective release.
163   *
164   * @return the effective release.
165   * @since 0.0.1
166   */
167  String getEffectiveRelease();
168
169  /**
170   * Get a file for input operations.
171   *
172   * @param location     a package-oriented location.
173   * @param packageName  a package name.
174   * @param relativeName a relative name.
175   * @return the file object, or {@code null} if not found or not available.
176   * @throws IOException              if an IO error occurs reading the file details.
177   * @throws IllegalArgumentException if the location is not a package-oriented location.
178   */
179  @Nullable
180  @Override
181  FileObject getFileForInput(
182      Location location,
183      String packageName,
184      String relativeName
185  ) throws IOException;
186
187  /**
188   * Get a file for output operations.
189   *
190   * @param location     an output location.
191   * @param packageName  a package name.
192   * @param relativeName a relative name.
193   * @param sibling      a file object to be used as hint for placement, might be {@code null}.
194   * @return the file object, or {@code null} if not found or not available.
195   * @throws IOException              if an IO error occurs reading the file details.
196   * @throws IllegalArgumentException if the location is not an output location.
197   */
198  @Nullable
199  @Override
200  FileObject getFileForOutput(
201      Location location,
202      String packageName,
203      String relativeName,
204      @Nullable FileObject sibling
205  ) throws IOException;
206
207  /**
208   * Get a Java file for input operations.
209   *
210   * @param location  a package-oriented location.
211   * @param className the name of a class.
212   * @param kind      the kind of file.
213   * @return the file object, or {@code null} if not found or not available.
214   * @throws IOException              if an IO error occurs reading the file details.
215   * @throws IllegalArgumentException if the location is not a package-oriented location.
216   */
217  @Nullable
218  @Override
219  JavaFileObject getJavaFileForInput(
220      Location location,
221      String className,
222      Kind kind
223  ) throws IOException;
224
225  /**
226   * Get a Java file for output operations.
227   *
228   * @param location  an output location.
229   * @param className the name of a class.
230   * @param kind      the kind of file.
231   * @param sibling   a file object to be used as hint for placement, might be {@code null}.
232   * @return the file object, or {@code null} if not found or not available.
233   * @throws IOException              if an IO error occurs reading the file details.
234   * @throws IllegalArgumentException if the location is not an output location.
235   */
236  @Nullable
237  @Override
238  JavaFileObject getJavaFileForOutput(
239      Location location,
240      String className,
241      Kind kind,
242      @Nullable FileObject sibling
243  ) throws IOException;
244
245  /**
246   * Get the location for a named module within the given location.
247   *
248   * @param location   the module-oriented location.
249   * @param moduleName the name of the module to be found.
250   * @return the location of the named module, or {@code null} if not found.
251   * @throws IOException              if an IO error occurs during resolution.
252   * @throws IllegalArgumentException if the location is neither an output location nor a
253   *                                  module-oriented location.
254   */
255  @Nullable
256  @Override
257  Location getLocationForModule(Location location, String moduleName) throws IOException;
258
259  /**
260   * Get the location for the module holding a given file object within the given location.
261   *
262   * @param location   the module-oriented location.
263   * @param fileObject the file object to resolve the module for.
264   * @return the location, or {@code null} if not found.
265   * @throws IOException              if an IO error occurs during resolution.
266   * @throws IllegalArgumentException if the location is neither an output location nor a
267   *                                  module-oriented location.
268   */
269  @Nullable
270  @Override
271  Location getLocationForModule(Location location, JavaFileObject fileObject) throws IOException;
272
273  /**
274   * Get the container group for the given package-oriented location.
275   *
276   * @param location the package oriented location.
277   * @return the container group, or null if one does not exist.
278   */
279  @Nullable
280  PackageContainerGroup getPackageContainerGroup(Location location);
281
282  /**
283   * Get a collection of all package container groups in this file manager.
284   *
285   * @return the package container groups.
286   */
287  Collection<PackageContainerGroup> getPackageContainerGroups();
288
289  /**
290   * Get the container group for the given module-oriented location.
291   *
292   * @param location the module oriented location.
293   * @return the container group, or null if one does not exist.
294   */
295  @Nullable
296  ModuleContainerGroup getModuleContainerGroup(Location location);
297
298  /**
299   * Get a collection of all module container groups in this file manager.
300   *
301   * @return the module container groups.
302   */
303  Collection<ModuleContainerGroup> getModuleContainerGroups();
304
305  /**
306   * Get the container group for the given output-oriented location.
307   *
308   * @param location the output oriented location.
309   * @return the container group, or null if one does not exist.
310   */
311  @Nullable
312  OutputContainerGroup getOutputContainerGroup(Location location);
313
314  /**
315   * Get a collection of all output container groups in this file manager.
316   *
317   * @return the output container groups.
318   */
319  Collection<OutputContainerGroup> getOutputContainerGroups();
320
321  /**
322   * Handles one option.  If {@code current} is an option to this file manager it will consume any
323   * arguments to that option from {@code remaining} and return true, otherwise return false.
324   *
325   * @param current   current option.
326   * @param remaining remaining options.
327   * @return {@code true} if this option was handled by this file manager, {@code false} otherwise.
328   * @throws IllegalArgumentException if this option to this file manager is used incorrectly.
329   * @throws IllegalStateException    if {@link #close} has been called and this file manager cannot
330   *                                  be reopened.
331   */
332  boolean handleOption(String current, Iterator<String> remaining);
333
334  /**
335   * Determines if a location is known to this file manager.
336   *
337   * @param location a location.
338   * @return true if the location is known to this file manager.
339   */
340  boolean hasLocation(Location location);
341
342  /**
343   * Infers a binary name of a file object based on a package-oriented location. The binary name
344   * returned might not be a valid binary name according to the Java Language Specification.
345   *
346   * @param location a location.
347   * @param file     a file object.
348   * @return a binary name or {@code null} the file object is not found in the given location
349   * @throws IllegalArgumentException if the location is a module-oriented location.
350   * @throws IllegalStateException    if {@link #close} has been called and this file manager cannot
351   *                                  be reopened.
352   */
353  @Nullable
354  @Override
355  String inferBinaryName(Location location, JavaFileObject file);
356
357  /**
358   * Infer the name of the module from its location, as returned by {@code getLocationForModule} or
359   * {@code listModuleLocations}.
360   *
361   * @param location a package-oriented location representing a module.
362   * @return the name of the module, or {@code null} if the name cannot be resolved or does not
363   *     exist.
364   * @throws IOException              if an I/O error occurred during resolution.
365   * @throws IllegalArgumentException if the location is not one known to this file manager.
366   */
367  @Nullable
368  @Override
369  String inferModuleName(Location location) throws IOException;
370
371  /**
372   * Determine if the two file objects are the same file.
373   *
374   * @param a a file object, can be {@code null}.
375   * @param b a file object, can be {@code null}.
376   * @return {@code true} if both arguments are non-null and refer to the same logical file, or
377   *     {@code false} otherwise.
378   */
379  @Override
380  boolean isSameFile(@Nullable FileObject a, @Nullable FileObject b);
381
382  /**
383   * List all file objects in the given location matching the given criteria.
384   *
385   * @param location    the location to search in.
386   * @param packageName the package name to search in, or {@code ""} to search in the root
387   *                    location.
388   * @param kinds       the kinds of file to return, or {@link Set#of} {@code (} {@link Kind#OTHER}
389   *                    {@code )} to find all files.
390   * @param recurse     {@code true} to recurse into subpackages, {@code false} to only check the
391   *                    current package.
392   * @return a collection of unique file objects that were found.
393   * @throws IOException              if an IO error occurs reading the file system.
394   * @throws IllegalArgumentException if the location is a module-oriented location
395   * @throws IllegalStateException    if {@link #close} has been called and this file manager cannot
396   *                                  be reopened
397   */
398  @Override
399  Set<JavaFileObject> list(
400      Location location,
401      String packageName,
402      Set<Kind> kinds,
403      boolean recurse
404  ) throws IOException;
405
406  /**
407   * List all module-based locations for the given module-oriented or output location.
408   *
409   * @param location the module-oriented/output location for which to list the modules.
410   * @return the iterable of sets of modules.
411   * @throws IOException              if an IO error occurs during resolution.
412   * @throws IllegalArgumentException if the location is not a module-oriented or output location.
413   */
414  @Override
415  Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException;
416
417  ///
418  /// Default helper overrides
419  ///
420
421  /**
422   * Get the location holding the {@link StandardLocation#CLASS_OUTPUT class outputs}.
423   *
424   * @return the location, or {@code null} if the location is not present in the file manager.
425   * @since 0.1.0
426   */
427  @Nullable
428  default OutputContainerGroup getClassOutputGroup() {
429    return getOutputContainerGroup(StandardLocation.CLASS_OUTPUT);
430  }
431
432  /**
433   * Get the location holding the {@link StandardLocation#SOURCE_OUTPUT source outputs}.
434   *
435   * @return the location, or {@code null} if the location is not present in the file manager.
436   * @since 0.1.0
437   */
438  @Nullable
439  default OutputContainerGroup getSourceOutputGroup() {
440    return getOutputContainerGroup(StandardLocation.SOURCE_OUTPUT);
441  }
442
443  /**
444   * Get the location holding the {@link StandardLocation#CLASS_PATH class path}.
445   *
446   * @return the location, or {@code null} if the location is not present in the file manager.
447   * @since 0.1.0
448   */
449  @Nullable
450  default PackageContainerGroup getClassPathGroup() {
451    return getPackageContainerGroup(StandardLocation.CLASS_PATH);
452  }
453
454  /**
455   * Get the location holding the {@link StandardLocation#SOURCE_PATH source path}.
456   *
457   * @return the location, or {@code null} if the location is not present in the file manager.
458   * @since 0.1.0
459   */
460  @Nullable
461  default PackageContainerGroup getSourcePathGroup() {
462    return getPackageContainerGroup(StandardLocation.SOURCE_PATH);
463  }
464
465  /**
466   * Get the location holding the
467   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
468   *
469   * @return the location, or {@code null} if the location is not present in the file manager.
470   * @since 0.1.0
471   */
472  @Nullable
473  default PackageContainerGroup getAnnotationProcessorPathGroup() {
474    return getPackageContainerGroup(StandardLocation.ANNOTATION_PROCESSOR_PATH);
475  }
476
477  /**
478   * Get the location holding the
479   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
480   *
481   * @return the location, or {@code null} if the location is not present in the file manager.
482   * @since 0.1.0
483   */
484  @Nullable
485  default ModuleContainerGroup getAnnotationProcessorModulePathGroup() {
486    return getModuleContainerGroup(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH);
487  }
488
489  /**
490   * Get the location holding the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
491   *
492   * @return the location, or {@code null} if the location is not present in the file manager.
493   * @since 0.1.0
494   */
495  @Nullable
496  default ModuleContainerGroup getModuleSourcePathGroup() {
497    return getModuleContainerGroup(StandardLocation.MODULE_SOURCE_PATH);
498  }
499
500  /**
501   * Get the location holding the {@link StandardLocation#MODULE_PATH module path}.
502   *
503   * @return the location, or {@code null} if the location is not present in the file manager.
504   * @since 0.1.0
505   */
506  @Nullable
507  default ModuleContainerGroup getModulePathGroup() {
508    return getModuleContainerGroup(StandardLocation.MODULE_PATH);
509  }
510}