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