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