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