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.containers;
017
018import io.github.ascopes.jct.filemanagers.PathFileObject;
019import io.github.ascopes.jct.workspaces.PathRoot;
020import java.io.Closeable;
021import java.io.IOException;
022import java.lang.module.ModuleFinder;
023import java.nio.file.Path;
024import java.util.Collection;
025import java.util.Set;
026import javax.tools.FileObject;
027import javax.tools.JavaFileManager.Location;
028import javax.tools.JavaFileObject;
029import javax.tools.JavaFileObject.Kind;
030import org.apiguardian.api.API;
031import org.apiguardian.api.API.Status;
032import org.jspecify.annotations.Nullable;
033
034/**
035 * Container that wraps a file path source of some description.
036 *
037 * <p>Closing this resource will close any internally held resources that were opened internally.
038 * Already-opened resources passed to the implementation will not be closed.
039 *
040 * @author Ashley Scopes
041 * @since 0.0.1
042 */
043@API(since = "0.0.1", status = Status.STABLE)
044public interface Container extends Closeable {
045
046  /**
047   * Determine if this container contains the path file object.
048   *
049   * @param fileObject the file object to look up.
050   * @return {@code true} if the object exists in this container, or {@code false} otherwise.
051   */
052  boolean contains(PathFileObject fileObject);
053
054  /**
055   * Find the physical path to a given string file path.
056   *
057   * <p>Examples:
058   *
059   * <pre><code>
060   *   // Using platform-specific separators ('/' for in-memory file systems).
061   *   container.getFile("foo/bar/baz.txt");
062   *
063   *   // Letting JCT infer the correct path separators to use.
064   *   container.getFile("foo", "bar", "baz.txt");
065   * </code></pre>
066   *
067   * @param fragments parts of the file name to find.
068   * @return the path if the file exists, or null if it does not exist.
069   * @throws NullPointerException     if any of the path fragments are {@code null}.
070   * @throws IllegalArgumentException if there are no path fragments provided.
071   */
072  @Nullable
073  Path getFile(String... fragments);
074
075  /**
076   * Get a {@link FileObject} for reading, if it exists.
077   *
078   * <p>If the file does not exist, {@code null} is returned.
079   *
080   * @param packageName  the package name of the file.
081   * @param relativeName the relative name of the file in the package.
082   * @return the file object, or null if it does not exist.
083   */
084  @Nullable
085  PathFileObject getFileForInput(String packageName, String relativeName);
086
087  /**
088   * Get a {@link FileObject} for writing.
089   *
090   * <p>If the container is read-only, {@code null} is returned.
091   *
092   * @param packageName  the package name of the file.
093   * @param relativeName the relative name of the file in the package.
094   * @return the file object, or null if this container is read-only.
095   */
096  @Nullable
097  PathFileObject getFileForOutput(String packageName, String relativeName);
098
099  /**
100   * Get the inner path root of the container.
101   *
102   * <p>This will usually be the same as the {@link #getPathRoot() path root}, but for archives,
103   * this will point to the root directory of the archive for the virtual file system it is loaded
104   * in, rather than the location of the archive on the original file system.
105   *
106   * <p>It is worth noting that this operation may result in the archive being loaded into memory
107   * eagerly if it uses lazy loading, due to the need to load the archive to be able to determine an
108   * accurate handle to the inner root directory.
109   *
110   * @return the path root.
111   * @since 0.0.6
112   */
113  @API(since = "0.0.6", status = Status.STABLE)
114  PathRoot getInnerPathRoot();
115
116  /**
117   * Get a {@link JavaFileObject} for reading, if it exists.
118   *
119   * <p>If the file does not exist, {@code null} is returned.
120   *
121   * @param className the binary name of the class to open.
122   * @param kind      the kind of file to open.
123   * @return the file object, or null if it does not exist.
124   */
125  @Nullable
126  PathFileObject getJavaFileForInput(String className, Kind kind);
127
128  /**
129   * Get a {@link JavaFileObject} for writing.
130   *
131   * <p>If the container is read-only, {@code null} is returned.
132   *
133   * @param className the binary name of the class to open.
134   * @param kind      the kind of file to open.
135   * @return the file object, or null if this container is read-only.
136   */
137  @Nullable
138  PathFileObject getJavaFileForOutput(String className, Kind kind);
139
140  /**
141   * Get the location of this container.
142   *
143   * @return the location.
144   */
145  Location getLocation();
146
147  /**
148   * Get a module finder for this container.
149   *
150   * <p>Note that this will not detect modules that are not yet compiled.
151   *
152   * @return the module finder for this container, or {@code null} if not relevant to the
153   *     implementation.
154   */
155  @Nullable
156  ModuleFinder getModuleFinder();
157
158  /**
159   * Get the name of this container for human consumption.
160   *
161   * @return the container name.
162   */
163  String getName();
164
165  /**
166   * Get the path root of the container.
167   *
168   * @return the path root.
169   */
170  PathRoot getPathRoot();
171
172  /**
173   * Infer the binary name of a given Java file object.
174   *
175   * @param javaFileObject the Java file object to infer the binary name of.
176   * @return the name, or null if the file does not exist in this container.
177   */
178  @Nullable
179  String inferBinaryName(PathFileObject javaFileObject);
180
181  /**
182   * List all files within this container.
183   *
184   * @return all files within the container, relative to the container base path.
185   * @throws IOException if the file system failed to be read.
186   */
187  Collection<Path> listAllFiles() throws IOException;
188
189  /**
190   * List all the file objects that match the given criteria in this group.
191   *
192   * <p>The results are filled into a given collection, since this call may be made many times
193   * per compilation, and this reduces the memory overhead needed in such cases.
194   *
195   * @param packageName the package name to look in.
196   * @param kinds       the kinds of file to look for. Set to {@code Set.of(Kind.OTHER)} to find all
197   *                    types of file.
198   * @param recurse     {@code true} to recurse subpackages, {@code false} to only consider the
199   *                    given package.
200   * @param collection  the collection to fill.
201   * @throws IOException if the file system failed to be read.
202   */
203  void listFileObjects(
204      String packageName,
205      Set<? extends Kind> kinds,
206      boolean recurse,
207      Collection<JavaFileObject> collection
208  ) throws IOException;
209}