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}