001/*
002 * Copyright (C) 2022 - 2023, 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.Set;
026import javax.tools.JavaFileManager;
027import javax.tools.JavaFileObject;
028import javax.tools.JavaFileObject.Kind;
029import javax.tools.StandardLocation;
030import org.apiguardian.api.API;
031import org.apiguardian.api.API.Status;
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 */
047@API(since = "0.0.1", status = Status.STABLE)
048public interface JctFileManager extends JavaFileManager {
049
050  /**
051   * Add a package-oriented path to a given location.
052   *
053   * <p>To add a module, first obtain the module location using
054   * {@link #getLocationForModule(Location, String)}, and pass that result to this call.
055   *
056   * @param location the location to use.
057   * @param path     the path to add.
058   * @see #getLocationForModule(Location, String)
059   */
060  void addPath(Location location, PathRoot path);
061
062  /**
063   * Add a collection of package-oriented paths to a given location.
064   *
065   * <p>To add a module, first obtain the module location using
066   * {@link #getLocationForModule(Location, String)}, and pass that result to this call.
067   *
068   * @param location the location to use.
069   * @param paths    the paths to add.
070   * @see #getLocationForModule(Location, String)
071   */
072  void addPaths(Location location, Collection<? extends PathRoot> paths);
073
074  /**
075   * Copy all containers from the first location to the second location.
076   *
077   * @param from the first location.
078   * @param to   the second location.
079   */
080  void copyContainers(Location from, Location to);
081
082  /**
083   * Register an empty container for the given location to indicate to the compiler that the feature
084   * exists, but has no configured paths.
085   *
086   * <p>This is needed to coerce the behaviour for annotation processing in some cases.
087   *
088   * <p>If the location already exists, then do not do anything.
089   *
090   * <p>If the location is an output location, then this operation does not make any sense, since
091   * an empty location cannot have files output to it. In this case, you will likely get an
092   * exception.
093   *
094   * <p>Likewise, this operation does not make sense for module locations within a module-oriented
095   * location group, so this operation will fail with an error for those inputs as well.
096   *
097   * @param location the location to apply an empty container for.
098   * @throws IllegalArgumentException if the location is an output location or a module location.
099   */
100  void createEmptyLocation(Location location);
101
102  /**
103   * Get the associated effective release.
104   *
105   * @return the effective release.
106   * @since 0.0.1
107   */
108  String getEffectiveRelease();
109
110  /**
111   * Get the container group for the given package-oriented location.
112   *
113   * @param location the package oriented location.
114   * @return the container group, or null if one does not exist.
115   */
116  PackageContainerGroup getPackageContainerGroup(Location location);
117
118  /**
119   * Get a collection of all package container impl in this file manager.
120   *
121   * @return the package container impl.
122   */
123  Collection<PackageContainerGroup> getPackageContainerGroups();
124
125  /**
126   * Get the container group for the given module-oriented location.
127   *
128   * @param location the module oriented location.
129   * @return the container group, or null if one does not exist.
130   */
131  ModuleContainerGroup getModuleContainerGroup(Location location);
132
133  /**
134   * Get a collection of all module container impl in this file manager.
135   *
136   * @return the module container impl.
137   */
138  Collection<ModuleContainerGroup> getModuleContainerGroups();
139
140  /**
141   * Get the container group for the given output-oriented location.
142   *
143   * @param location the output oriented location.
144   * @return the container group, or null if one does not exist.
145   */
146  OutputContainerGroup getOutputContainerGroup(Location location);
147
148  /**
149   * Get a collection of all output container impl in this file manager.
150   *
151   * @return the output container impl.
152   */
153  Collection<OutputContainerGroup> getOutputContainerGroups();
154
155  /**
156   * List all file objects in the given location matching the given criteria.
157   *
158   * @param location    the location to search in.
159   * @param packageName the package name to search in, or {@code ""} to search in the root
160   *                    location.
161   * @param kinds       the kinds of file to return, or {@link Set#of} {@code (} {@link Kind#OTHER}
162   *                    {@code )} to find all files.
163   * @param recurse     {@code true} to recurse into subpackages, {@code false} to only check the
164   *                    current package.
165   * @return a collection of unique file objects that were found.
166   * @throws IOException if an IO error occurs reading the file system.
167   */
168  @Override
169  Set<JavaFileObject> list(
170      Location location,
171      String packageName,
172      Set<Kind> kinds,
173      boolean recurse
174  ) throws IOException;
175
176  ///
177  /// Default helper overrides
178  ///
179
180  /**
181   * Get the location holding the {@link StandardLocation#CLASS_OUTPUT class outputs}.
182   *
183   * @return the location, or {@code null} if the location is not present in the file manager.
184   * @since 0.1.0
185   */
186  @Nullable
187  default OutputContainerGroup getClassOutputGroup() {
188    return getOutputContainerGroup(StandardLocation.CLASS_OUTPUT);
189  }
190
191  /**
192   * Get the location holding the {@link StandardLocation#SOURCE_OUTPUT source outputs}.
193   *
194   * @return the location, or {@code null} if the location is not present in the file manager.
195   * @since 0.1.0
196   */
197  @Nullable
198  default OutputContainerGroup getSourceOutputGroup() {
199    return getOutputContainerGroup(StandardLocation.SOURCE_OUTPUT);
200  }
201
202  /**
203   * Get the location holding the {@link StandardLocation#CLASS_PATH class path}.
204   *
205   * @return the location, or {@code null} if the location is not present in the file manager.
206   * @since 0.1.0
207   */
208  @Nullable
209  default PackageContainerGroup getClassPathGroup() {
210    return getPackageContainerGroup(StandardLocation.CLASS_PATH);
211  }
212
213  /**
214   * Get the location holding the {@link StandardLocation#SOURCE_PATH source path}.
215   *
216   * @return the location, or {@code null} if the location is not present in the file manager.
217   * @since 0.1.0
218   */
219  @Nullable
220  default PackageContainerGroup getSourcePathGroup() {
221    return getPackageContainerGroup(StandardLocation.SOURCE_PATH);
222  }
223
224  /**
225   * Get the location holding the
226   * {@link StandardLocation#ANNOTATION_PROCESSOR_PATH annotation processor path}.
227   *
228   * @return the location, or {@code null} if the location is not present in the file manager.
229   * @since 0.1.0
230   */
231  @Nullable
232  default PackageContainerGroup getAnnotationProcessorPathGroup() {
233    return getPackageContainerGroup(StandardLocation.ANNOTATION_PROCESSOR_PATH);
234  }
235
236  /**
237   * Get the location holding the
238   * {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH annotation processor module path}.
239   *
240   * @return the location, or {@code null} if the location is not present in the file manager.
241   * @since 0.1.0
242   */
243  @Nullable
244  default ModuleContainerGroup getAnnotationProcessorModulePathGroup() {
245    return getModuleContainerGroup(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH);
246  }
247
248  /**
249   * Get the location holding the {@link StandardLocation#PLATFORM_CLASS_PATH platform class path}
250   * (also known as the boot class path).
251   *
252   * @return the location, or {@code null} if the location is not present in the file manager.
253   * @since 0.1.0
254   * @deprecated The platform class path has been mostly replaced by the use of system modules, so
255   *    should not be used. This will be removed in v1.0.0.
256   */
257  @Deprecated(forRemoval = true, since = "0.6.0")
258  @Nullable
259  @SuppressWarnings("DeprecatedIsStillUsed")
260  default PackageContainerGroup getPlatformClassPathGroup() {
261    return getPackageContainerGroup(StandardLocation.PLATFORM_CLASS_PATH);
262  }
263
264  /**
265   * Get the location holding the
266   * {@link StandardLocation#NATIVE_HEADER_OUTPUT native header outputs}.
267   *
268   * @return the location, or {@code null} if the location is not present in the file manager.
269   * @since 0.1.0
270   * @deprecated This is a specialist method that is usually not used. To keep the API simple,
271   * this is being removed. You should instead call {@link #getOutputContainerGroup(Location)},
272   * passing {@link StandardLocation#NATIVE_HEADER_OUTPUT} as the first parameter.
273   */
274  @Deprecated(since = "0.6.0", forRemoval = true)
275  @Nullable
276  @SuppressWarnings("DeprecatedIsStillUsed")
277  default OutputContainerGroup getNativeHeaderOutputGroup() {
278    return getOutputContainerGroup(StandardLocation.NATIVE_HEADER_OUTPUT);
279  }
280
281  /**
282   * Get the location holding the {@link StandardLocation#MODULE_SOURCE_PATH module source path}.
283   *
284   * @return the location, or {@code null} if the location is not present in the file manager.
285   * @since 0.1.0
286   */
287  @Nullable
288  default ModuleContainerGroup getModuleSourcePathGroup() {
289    return getModuleContainerGroup(StandardLocation.MODULE_SOURCE_PATH);
290  }
291
292  /**
293   * Get the location holding the {@link StandardLocation#UPGRADE_MODULE_PATH upgrade module path}.
294   *
295   * @return the location, or {@code null} if the location is not present in the file manager.
296   * @since 0.1.0
297   * @deprecated This is a specialist method that is usually not used. To keep the API simple,
298   * this is being removed. You should instead call {@link #getModuleContainerGroup(Location)},
299   * passing {@link StandardLocation#SYSTEM_MODULES} as the first parameter.
300   */
301  @Deprecated(since = "0.6.0", forRemoval = true)
302  @Nullable
303  @SuppressWarnings("DeprecatedIsStillUsed")
304  default ModuleContainerGroup getUpgradeModulePathGroup() {
305    return getModuleContainerGroup(StandardLocation.UPGRADE_MODULE_PATH);
306  }
307
308  /**
309   * Get the location holding the {@link StandardLocation#SYSTEM_MODULES system modules}.
310   *
311   * @return the location, or {@code null} if the location is not present in the file manager.
312   * @since 0.1.0
313   * @deprecated This is a specialist method that is usually not used. To keep the API simple,
314   * this is being removed. You should instead call {@link #getModuleContainerGroup(Location)},
315   * passing {@link StandardLocation#SYSTEM_MODULES} as the first parameter.
316   */
317  @Deprecated(since = "0.6.0", forRemoval = true)
318  @Nullable
319  @SuppressWarnings("DeprecatedIsStillUsed")
320  default ModuleContainerGroup getSystemModulesGroup() {
321    return getModuleContainerGroup(StandardLocation.SYSTEM_MODULES);
322  }
323
324  /**
325   * Get the location holding the {@link StandardLocation#MODULE_PATH module path}.
326   *
327   * @return the location, or {@code null} if the location is not present in the file manager.
328   * @since 0.1.0
329   */
330  @Nullable
331  default ModuleContainerGroup getModulePathGroup() {
332    return getModuleContainerGroup(StandardLocation.MODULE_PATH);
333  }
334
335  /**
336   * Get the location holding the {@link StandardLocation#PATCH_MODULE_PATH patch module path}.
337   *
338   * @return the location, or {@code null} if the location is not present in the file manager.
339   * @since 0.1.0
340   * @deprecated This is a specialist method that is usually not used. To keep the API simple,
341   * this is being removed. You should instead call {@link #getModuleContainerGroup(Location)},
342   * passing {@link StandardLocation#PATCH_MODULE_PATH} as the first parameter.
343   */
344  @Deprecated(since = "0.6.0", forRemoval = true)
345  @Nullable
346  @SuppressWarnings("DeprecatedIsStillUsed")
347  default ModuleContainerGroup getPatchModulePathGroup() {
348    return getModuleContainerGroup(StandardLocation.PATCH_MODULE_PATH);
349  }
350}