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 @SuppressWarnings("DataFlowIssue") 058 protected AbstractJavaFileObjectAssert(@Nullable A actual, Class<?> selfType) { 059 super(actual, selfType); 060 } 061 062 /** 063 * Get an assertion object on the URI of the file. 064 * 065 * @return the URI assertion. 066 * @throws AssertionError if the actual value is null. 067 */ 068 public AbstractUriAssert<?> uri() { 069 isNotNull(); 070 return assertThat(actual.toUri()); 071 } 072 073 /** 074 * Get an assertion object on the name of the file. 075 * 076 * @return the string assertion. 077 * @throws AssertionError if the actual value is null. 078 */ 079 public AbstractStringAssert<?> name() { 080 isNotNull(); 081 return assertThat(actual.getName()); 082 } 083 084 /** 085 * Get an assertion object on the binary content of the file. 086 * 087 * @return the byte array assertion. 088 * @throws AssertionError if the actual value is null. 089 */ 090 public AbstractByteArrayAssert<?> binaryContent() { 091 isNotNull(); 092 return assertThat(rawContent()); 093 } 094 095 /** 096 * Get an assertion object on the content of the file, using {@link StandardCharsets#UTF_8 UTF-8} 097 * encoding. 098 * 099 * @return the string assertion. 100 * @throws AssertionError if the actual value is null. 101 * @throws UncheckedIOException if an IO error occurs reading the file content. 102 */ 103 public AbstractStringAssert<?> content() { 104 return content(StandardCharsets.UTF_8); 105 } 106 107 /** 108 * Get an assertion object on the content of the file. 109 * 110 * @param charset the charset to decode the file with. 111 * @return the string assertion. 112 * @throws AssertionError if the actual value is null. 113 * @throws NullPointerException if the charset parameter is null. 114 * @throws UncheckedIOException if an IO error occurs reading the file content. 115 */ 116 public AbstractStringAssert<?> content(Charset charset) { 117 requireNonNull(charset, "charset must not be null"); 118 return content(charset.newDecoder()); 119 } 120 121 /** 122 * Get an assertion object on the content of the file. 123 * 124 * @param charsetDecoder the charset decoder to use to decode the file to a string. 125 * @return the string assertion. 126 * @throws AssertionError if the actual value is null. 127 * @throws NullPointerException if the charset decoder parameter is null. 128 * @throws UncheckedIOException if an IO error occurs reading the file content. 129 */ 130 public AbstractStringAssert<?> content(CharsetDecoder charsetDecoder) { 131 requireNonNull(charsetDecoder, "charsetDecoder must not be null"); 132 isNotNull(); 133 134 var content = uncheckedIo(() -> charsetDecoder 135 .decode(ByteBuffer.wrap(rawContent())) 136 .toString()); 137 138 return assertThat(content); 139 } 140 141 /** 142 * Get an assertion object on the last modified timestamp. 143 * 144 * <p>This will be set to the UNIX epoch ({@code 1970-01-01T00:00:00.000Z}) if an 145 * error occurs reading the file modification time, or if the information is not available. 146 * 147 * @return the instant assertion. 148 * @throws AssertionError if the actual value is null. 149 */ 150 public AbstractInstantAssert<?> lastModified() { 151 isNotNull(); 152 153 var instant = Instant.ofEpochMilli(actual.getLastModified()); 154 return assertThat(instant); 155 } 156 157 /** 158 * Perform an assertion on the file object kind. 159 * 160 * @return the assertions for the kind. 161 * @throws AssertionError if the actual value is null. 162 */ 163 public JavaFileObjectKindAssert kind() { 164 isNotNull(); 165 166 return new JavaFileObjectKindAssert(actual.getKind()); 167 } 168 169 private byte[] rawContent() { 170 return uncheckedIo(() -> { 171 var outputStream = new ByteArrayOutputStream(); 172 try (var inputStream = actual.openInputStream()) { 173 inputStream.transferTo(outputStream); 174 return outputStream.toByteArray(); 175 } 176 }); 177 } 178}