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