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.workspaces;
017
018import java.io.File;
019import java.io.InputStream;
020import java.net.URL;
021import java.nio.charset.Charset;
022import java.nio.file.Path;
023import org.apiguardian.api.API;
024import org.apiguardian.api.API.Status;
025
026/**
027 * Chainable builder for creating individual files.
028 *
029 * @author Ashley Scopes
030 * @since 0.0.1
031 */
032@API(since = "0.0.1", status = Status.STABLE)
033@SuppressWarnings("unused")
034public interface FileBuilder {
035
036  /**
037   * Take the directory, represented by the given {@link ManagedDirectory}, and convert it into a
038   * JAR file, that will be written to the file being created.
039   *
040   * <pre><code>
041   *   // Compile some files first.
042   *   Workspace firstWorkspace = ...;
043   *   JctCompilation firstCompilation = firstCompiler.compile(firstWorkspace);
044   *   ManagedDirectory firstClassOutputs = firstWorkspace.getClassOutputPackage();
045   *
046   *   // Wrap the first compilation outputs in a JAR and add as a class path dependency
047   *   // to the second compilation.
048   *   Workspace secondWorkspace = ...;
049   *   secondWorkspace
050   *       .createFile("first.jar")
051   *       .asJarFrom(firstClassOutputs);
052   *   JctCompilation secondCompilation = secondCompiler.compile(secondWorkspace);
053   *   ...
054   * </code></pre>
055   *
056   * @param directory the managed directory to use.
057   * @return the root managed directory for further configuration.
058   */
059  @API(since = "0.3.0", status = Status.STABLE)
060  ManagedDirectory asJarFrom(ManagedDirectory directory);
061
062  /**
063   * Take the directory, represented by the given path, and convert it into a JAR file, that will be
064   * written to the file being created.
065   *
066   * <pre><code>
067   *   ManagedDirectory someDirectory = ...;
068   *   someDirectory
069   *       .createFile("first.jar")
070   *       .asJarFrom(firstClassOutputs);
071   *   var secondCompilation = secondCompiler.compile(secondWorkspace);
072   *   ...
073   * </code></pre>
074   *
075   * @param directory the directory to use.
076   * @return the root managed directory for further configuration.
077   */
078  @API(since = "0.3.0", status = Status.STABLE)
079  ManagedDirectory asJarFrom(Path directory);
080
081  /**
082   * Copy a resource from the given class loader into the file system.
083   *
084   * <pre><code>
085   *   var classLoader = getClass().getClassLoader();
086   *
087   *   directory
088   *       .createFile("org", "example", "HelloWorld.class")
089   *       .copiedFromClassPath(classLoader, "org/example/HelloWorld.class");
090   * </code></pre>
091   *
092   * @param classLoader the class loader to use.
093   * @param resource    the resource to copy.
094   * @return the root managed directory for further configuration.
095   */
096  ManagedDirectory copiedFromClassPath(ClassLoader classLoader, String resource);
097
098  /**
099   * Copy a resource from the class loader on the current thread into the file system.
100   *
101   * <pre><code>
102   *   directory
103   *       .createFile("org", "example", "HelloWorld.class")
104   *       .copiedFromClassPath("org/example/HelloWorld.class");
105   * </code></pre>
106   *
107   * @param resource the resource to copy.
108   * @return the root managed directory for further configuration.
109   */
110  ManagedDirectory copiedFromClassPath(String resource);
111
112  /**
113   * Copy the contents from the given file into the file system.
114   *
115   * <pre><code>
116   *   var file = new File("src/test/resources/code/HelloWorld.java");
117   *
118   *   directory
119   *       .createFile("org", "example", "HelloWorld.java")
120   *       .copiedFromFile(file);
121   * </code></pre>
122   *
123   * @param file the file to read.
124   * @return the root managed directory for further configuration.
125   */
126  ManagedDirectory copiedFromFile(File file);
127
128  /**
129   * Copy the contents from the given path into the file system.
130   *
131   * <pre><code>
132   *   var file = Path.of("src", "test", "resources", "code", "HelloWorld.java");
133   *
134   *   directory
135   *       .createFile("org", "example", "HelloWorld.java")
136   *       .copiedFromFile(file);
137   * </code></pre>
138   *
139   * @param file the file to read.
140   * @return the root managed directory for further configuration.
141   */
142  ManagedDirectory copiedFromFile(Path file);
143
144  /**
145   * Copy the contents from the given URL into the file system.
146   *
147   * <pre><code>
148   *   var url = URI.create("file:///code/org/example/HelloWorld.java").toURI();
149   *
150   *   directory
151   *       .createFile("org", "example", "HelloWorld.java")
152   *       .copiedFromUrl(url);
153   * </code></pre>
154   *
155   * @param url the URL to read.
156   * @return the root managed directory for further configuration.
157   */
158  ManagedDirectory copiedFromUrl(URL url);
159
160  /**
161   * Copy the contents from the given input stream into the file system.
162   *
163   * <p>The input stream will be closed when reading completes.
164   *
165   * <pre><code>
166   *   try (var is = getClass().getResourceAsStream("code/examples/HelloWorld.java")) {
167   *     directory
168   *         .createFile("org", "example", "HelloWorld.java")
169   *         .fromInputStream(is);
170   *   }
171   * </code></pre>
172   *
173   * @param inputStream the input stream to read.
174   * @return the root managed directory for further configuration.
175   */
176  ManagedDirectory fromInputStream(InputStream inputStream);
177
178  /**
179   * Create the file but do not put anything in it.
180   *
181   * <p>The resultant file will be 0 bytes long.
182   *
183   * <pre><code>
184   *   directory
185   *       .createFile(".gitkeep")
186   *       .thatIsEmpty();
187   * </code></pre>
188   *
189   * @return the root managed directory for further configuration.
190   */
191  ManagedDirectory thatIsEmpty();
192
193
194  /**
195   * Create the file with the given byte contents.
196   *
197   * <pre><code>
198   *   var classFile = new byte[]{
199   *      -54, -2, -70, -66, 0, 0, 0, 63, 0, 29, 10, 0, 2, 0, 3, 7, 0, 4, 12, 0, 5, 0, 6, 1, 0,
200   *      16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 6,
201   *      60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 9, 0, 8, 0, 9, 7, 0, 10, 12, 0, 11,
202   *      0, 12, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101,
203   *      109, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114,
204   *      105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 8, 0, 14, 1, 0, 13, 72, 101, 108, 108,
205   *      111, 44, 32, 87, 111, 114, 108, 100, 33, 10, 0, 16, 0, 17, 7, 0, 18, 12, 0, 19, 0, 20,
206   *      1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101,
207   *      97, 109, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76, 106, 97, 118, 97,
208   *      47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 7, 0, 22, 1, 0, 10,
209   *      72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76,
210   *      105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 4, 109, 97, 105,
211   *      110, 1, 0, 22, 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114,
212   *      105, 110, 103, 59, 41, 86, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1,
213   *      0, 15, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 46, 106, 97, 118, 97, 0, 33, 0,
214   *      21, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 23, 0, 0, 0, 29, 0, 1, 0, 1, 0,
215   *      0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 24, 0, 0, 0, 6, 0, 1, 0, 0, 0, 1, 0, 9, 0,
216   *      25, 0, 26, 0, 1, 0, 23, 0, 0, 0, 37, 0, 2, 0, 1, 0, 0, 0, 9, -78, 0, 7, 18, 13, -74, 0,
217   *      15, -79, 0, 0, 0, 1, 0, 24, 0, 0, 0, 10, 0, 2, 0, 0, 0, 3, 0, 8, 0, 4, 0, 1, 0, 27, 0,
218   *      0, 0, 2, 0, 28
219   *   };
220   *
221   *   directory
222   *       .createFile("HelloWorld.class")
223   *       .withContents(classFile);
224   * </code></pre>
225   *
226   * @param contents the bytes to write.
227   * @return the root managed directory for further configuration.
228   */
229  ManagedDirectory withContents(byte[] contents);
230
231  /**
232   * Create the file with the given contents.
233   *
234   * <pre><code>
235   *   directory
236   *      .createFile("org", "example", "HelloWorld.java")
237   *      .withContents(StandardCharsets.US_ASCII, """
238   *        package org.example;
239   *
240   *        public class HelloWorld {
241   *          public static void main(String[] args) {
242   *            System.out.println("Hello, World!");
243   *          }
244   *        }
245   *      """);
246   * </code></pre>
247   *
248   * <p>If the Java language level of your tests does not support multi-line strings, you can
249   * alternatively pass each line of text to write as a separate string. These will be written to
250   * the file using line-feed {@code '\n'} separators. For example:
251   *
252   * <pre><code>
253   *   directory
254   *      .createFile("org", "example", "HelloWorld.java")
255   *      .withContents(
256   *        StandardCharsets.US_ASCII,
257   *        "package org.example;",
258   *        "",
259   *        "public class HelloWorld {",
260   *        "  public static void main(String[] args) {",
261   *        "    System.out.println(\"Hello, World!\");",
262   *        "  }",
263   *        "}"
264   *      );
265   * </code></pre>
266   *
267   * @param charset the character encoding to use.
268   * @param lines   the lines to write.
269   * @return the root managed directory for further configuration.
270   * @see #withContents(String...)
271   * @see #withContents(byte[])
272   */
273  ManagedDirectory withContents(Charset charset, String... lines);
274
275  /**
276   * Create the file with the given contents as UTF-8.
277   *
278   * <p>If you are using multi-line strings, an example of usage would be:
279   *
280   * <pre><code>
281   *   directory
282   *      .createFile("org", "example", "HelloWorld.java")
283   *      .withContents("""
284   *        package org.example;
285   *
286   *        public class HelloWorld {
287   *          public static void main(String[] args) {
288   *            System.out.println("Hello, World!");
289   *          }
290   *        }
291   *      """);
292   * </code></pre>
293   *
294   * <p>If the Java language level of your tests does not support multi-line strings, you can
295   * alternatively pass each line of text to write as a separate string. These will be written to
296   * the file using line-feed {@code '\n'} separators. For example:
297   *
298   * <pre><code>
299   *   directory
300   *      .createFile("org", "example", "HelloWorld.java")
301   *      .withContents(
302   *        "package org.example;",
303   *        "",
304   *        "public class HelloWorld {",
305   *        "  public static void main(String[] args) {",
306   *        "    System.out.println(\"Hello, World!\");",
307   *        "  }",
308   *        "}"
309   *      );
310   * </code></pre>
311   *
312   * @param lines the lines to write using the default charset.
313   * @return the root managed directory for further configuration.
314   * @see #withContents(Charset, String...)
315   * @see #withContents(byte[])
316   */
317  ManagedDirectory withContents(String... lines);
318}