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.assertions; 017 018import static io.github.ascopes.jct.utils.IoExceptionUtils.uncheckedIo; 019import static java.util.Objects.requireNonNull; 020import static org.assertj.core.api.Assertions.assertThat; 021 022import java.io.ByteArrayOutputStream; 023import java.io.UncheckedIOException; 024import java.nio.ByteBuffer; 025import java.nio.charset.Charset; 026import java.nio.charset.CharsetDecoder; 027import java.nio.charset.StandardCharsets; 028import java.time.Instant; 029import javax.tools.JavaFileObject; 030import org.apiguardian.api.API; 031import org.apiguardian.api.API.Status; 032import org.assertj.core.api.AbstractAssert; 033import org.assertj.core.api.AbstractByteArrayAssert; 034import org.assertj.core.api.AbstractInstantAssert; 035import org.assertj.core.api.AbstractStringAssert; 036import org.assertj.core.api.AbstractUriAssert; 037import org.jspecify.annotations.Nullable; 038 039/** 040 * Abstract assertions for {@link JavaFileObject Java file objects}. 041 * 042 * @param <I> the implementation class that is extending this class. 043 * @param <A> the file object implementation type. 044 * @author Ashley Scopes 045 * @since 0.0.1 046 */ 047@API(since = "0.0.1", status = Status.STABLE) 048public abstract class AbstractJavaFileObjectAssert<I extends AbstractJavaFileObjectAssert<I, A>, A extends JavaFileObject> 049 extends AbstractAssert<I, A> { 050 051 /** 052 * Initialize this assertion. 053 * 054 * @param actual the actual value to assert on. 055 * @param selfType the type of the assertion implementation. 056 */ 057 protected AbstractJavaFileObjectAssert(@Nullable A actual, Class<?> selfType) { 058 super(actual, selfType); 059 } 060 061 /** 062 * Get an assertion object on the URI of the file. 063 * 064 * @return the URI assertion. 065 * @throws AssertionError if the actual value is null. 066 */ 067 public AbstractUriAssert<?> uri() { 068 isNotNull(); 069 return assertThat(actual.toUri()); 070 } 071 072 /** 073 * Get an assertion object on the name of the file. 074 * 075 * @return the string assertion. 076 * @throws AssertionError if the actual value is null. 077 */ 078 public AbstractStringAssert<?> name() { 079 isNotNull(); 080 return assertThat(actual.getName()); 081 } 082 083 /** 084 * Get an assertion object on the binary content of the file. 085 * 086 * @return the byte array assertion. 087 * @throws AssertionError if the actual value is null. 088 */ 089 public AbstractByteArrayAssert<?> binaryContent() { 090 isNotNull(); 091 return assertThat(rawContent()); 092 } 093 094 /** 095 * Get an assertion object on the content of the file, using {@link StandardCharsets#UTF_8 UTF-8} 096 * encoding. 097 * 098 * @return the string assertion. 099 * @throws AssertionError if the actual value is null. 100 * @throws UncheckedIOException if an IO error occurs reading the file content. 101 */ 102 public AbstractStringAssert<?> content() { 103 return content(StandardCharsets.UTF_8); 104 } 105 106 /** 107 * Get an assertion object on the content of the file. 108 * 109 * @param charset the charset to decode the file with. 110 * @return the string assertion. 111 * @throws AssertionError if the actual value is null. 112 * @throws NullPointerException if the charset parameter is null. 113 * @throws UncheckedIOException if an IO error occurs reading the file content. 114 */ 115 public AbstractStringAssert<?> content(Charset charset) { 116 requireNonNull(charset, "charset must not be null"); 117 return content(charset.newDecoder()); 118 } 119 120 /** 121 * Get an assertion object on the content of the file. 122 * 123 * @param charsetDecoder the charset decoder to use to decode the file to a string. 124 * @return the string assertion. 125 * @throws AssertionError if the actual value is null. 126 * @throws NullPointerException if the charset decoder parameter is null. 127 * @throws UncheckedIOException if an IO error occurs reading the file content. 128 */ 129 public AbstractStringAssert<?> content(CharsetDecoder charsetDecoder) { 130 requireNonNull(charsetDecoder, "charsetDecoder must not be null"); 131 isNotNull(); 132 133 var content = uncheckedIo(() -> charsetDecoder 134 .decode(ByteBuffer.wrap(rawContent())) 135 .toString()); 136 137 return assertThat(content); 138 } 139 140 /** 141 * Get an assertion object on the last modified timestamp. 142 * 143 * <p>This will be set to the UNIX epoch ({@code 1970-01-01T00:00:00.000Z}) if an 144 * error occurs reading the file modification time, or if the information is not available. 145 * 146 * @return the instant assertion. 147 * @throws AssertionError if the actual value is null. 148 */ 149 public AbstractInstantAssert<?> lastModified() { 150 isNotNull(); 151 152 var instant = Instant.ofEpochMilli(actual.getLastModified()); 153 return assertThat(instant); 154 } 155 156 /** 157 * Perform an assertion on the file object kind. 158 * 159 * @return the assertions for the kind. 160 * @throws AssertionError if the actual value is null. 161 */ 162 public JavaFileObjectKindAssert kind() { 163 isNotNull(); 164 165 return new JavaFileObjectKindAssert(actual.getKind()); 166 } 167 168 private byte[] rawContent() { 169 return uncheckedIo(() -> { 170 var baos = new ByteArrayOutputStream(); 171 try (var is = actual.openInputStream()) { 172 is.transferTo(baos); 173 return baos.toByteArray(); 174 } 175 }); 176 } 177}