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 fragment  the first part of the file name.
068   * @param fragments any additional parts of the file name to find.
069   * @return the path if the file exists, or null if it does not exist.
070   */
071  @Nullable
072  Path getFile(String fragment, String... fragments);
073
074  /**
075   * Get a {@link FileObject} for reading, if it exists.
076   *
077   * <p>If the file does not exist, {@code null} is returned.
078   *
079   * @param packageName  the package name of the file.
080   * @param relativeName the relative name of the file in the package.
081   * @return the file object, or null if it does not exist.
082   */
083  @Nullable
084  PathFileObject getFileForInput(String packageName, String relativeName);
085
086  /**
087   * Get a {@link FileObject} for writing.
088   *
089   * <p>If the container is read-only, {@code null} is returned.
090   *
091   * @param packageName  the package name of the file.
092   * @param relativeName the relative name of the file in the package.
093   * @return the file object, or null if this container is read-only.
094   */
095  @Nullable
096  PathFileObject getFileForOutput(String packageName, String relativeName);
097
098  /**
099   * Get the inner path root of the container.
100   *
101   * <p>This will usually be the same as the {@link #getPathRoot() path root}, but for archives,
102   * this will point to the root directory of the archive for the virtual file system it is loaded
103   * in, rather than the location of the archive on the original file system.
104   *
105   * <p>It is worth noting that this operation may result in the archive being loaded into memory
106   * eagerly if it uses lazy loading, due to the need to load the archive to be able to determine an
107   * accurate handle to the inner root directory.
108   *
109   * @return the path root.
110   * @since 0.0.6
111   */
112  @API(since = "0.0.6", status = Status.STABLE)
113  PathRoot getInnerPathRoot();
114
115  /**
116   * Get a {@link JavaFileObject} for reading, if it exists.
117   *
118   * <p>If the file does not exist, {@code null} is returned.
119   *
120   * @param className the binary name of the class to open.
121   * @param kind      the kind of file to open.
122   * @return the file object, or null if it does not exist.
123   */
124  @Nullable
125  PathFileObject getJavaFileForInput(String className, Kind kind);
126
127  /**
128   * Get a {@link JavaFileObject} for writing.
129   *
130   * <p>If the container is read-only, {@code null} is returned.
131   *
132   * @param className the binary name of the class to open.
133   * @param kind      the kind of file to open.
134   * @return the file object, or null if this container is read-only.
135   */
136  @Nullable
137  PathFileObject getJavaFileForOutput(String className, Kind kind);
138
139  /**
140   * Get the location of this container.
141   *
142   * @return the location.
143   */
144  Location getLocation();
145
146  /**
147   * Get a module finder for this container.
148   *
149   * <p>Note that this will not detect modules that are not yet compiled.
150   *
151   * @return the module finder for this container, or {@code null} if not relevant to the
152   *     implementation.
153   */
154  @Nullable
155  ModuleFinder getModuleFinder();
156
157  /**
158   * Get the name of this container for human consumption.
159   *
160   * @return the container name.
161   */
162  String getName();
163
164  /**
165   * Get the path root of the container.
166   *
167   * @return the path root.
168   */
169  PathRoot getPathRoot();
170
171  /**
172   * Infer the binary name of a given Java file object.
173   *
174   * @param javaFileObject the Java file object to infer the binary name of.
175   * @return the name, or null if the file does not exist in this container.
176   */
177  @Nullable
178  String inferBinaryName(PathFileObject javaFileObject);
179
180  /**
181   * List all files within this container.
182   *
183   * @return all files within the container, relative to the container base path.
184   * @throws IOException if the file system failed to be read.
185   */
186  Collection<Path> listAllFiles() throws IOException;
187
188  /**
189   * List all the file objects that match the given criteria in this group.
190   *
191   * <p>The results are filled into a given collection, since this call may be made many times
192   * per compilation, and this reduces the memory overhead needed in such cases.
193   *
194   * @param packageName the package name to look in.
195   * @param kinds       the kinds of file to look for. Set to {@code Set.of(Kind.OTHER)} to find all
196   *                    types of file.
197   * @param recurse     {@code true} to recurse subpackages, {@code false} to only consider the
198   *                    given package.
199   * @param collection  the collection to fill.
200   * @throws IOException if the file system failed to be read.
201   */
202  void listFileObjects(
203      String packageName,
204      Set<? extends Kind> kinds,
205      boolean recurse,
206      Collection<JavaFileObject> collection
207  ) throws IOException;
208}