001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.io; 018 019 020import java.io.*; 021import java.nio.charset.*; 022import java.nio.file.*; 023import static org.apache.juneau.commons.utils.Shorts.*; 024 025/** 026 * A fluent builder for creating {@link Reader} instances from files with configurable character encoding. 027 * 028 * <p> 029 * This builder provides a convenient way to create file readers with custom character encodings 030 * and optional handling for missing files. It's particularly useful when you need to read files 031 * with specific encodings or handle cases where files may not exist. 032 * 033 * <h5 class='section'>Features:</h5> 034 * <ul class='spaced-list'> 035 * <li>Fluent API - all methods return <c>this</c> for method chaining 036 * <li>Character encoding support - specify custom charset for file reading 037 * <li>Missing file handling - optional support for returning empty reader when file doesn't exist 038 * <li>Multiple file specification methods - accept File, String path, or Path 039 * </ul> 040 * 041 * <h5 class='section'>Use Cases:</h5> 042 * <ul class='spaced-list'> 043 * <li>Reading files with specific character encodings (UTF-8, ISO-8859-1, etc.) 044 * <li>Handling optional configuration files that may not exist 045 * <li>Creating readers with consistent encoding across an application 046 * <li>Reading files where encoding must be explicitly specified 047 * </ul> 048 * 049 * <h5 class='section'>Usage:</h5> 050 * <p class='bjava'> 051 * <jc>// Basic usage</jc> 052 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 053 * .file(<js>"/path/to/file.txt"</js>) 054 * .charset(<js>"UTF-8"</js>) 055 * .build(); 056 * 057 * <jc>// With missing file handling</jc> 058 * Reader <jv>reader2</jv> = FileReaderBuilder.<jsm>create</jsm>() 059 * .file(<js>"optional-config.properties"</js>) 060 * .allowNoFile() 061 * .build(); <jc>// Returns empty StringReader if file doesn't exist</jc> 062 * 063 * <jc>// Using File object</jc> 064 * File <jv>f</jv> = <jk>new</jk> File(<js>"data.txt"</js>); 065 * Reader <jv>reader3</jv> = FileReaderBuilder.<jsm>create</jsm>(<jv>f</jv>) 066 * .charset(StandardCharsets.UTF_8) 067 * .build(); 068 * </p> 069 * 070 * <h5 class='section'>Character Encoding:</h5> 071 * <p> 072 * By default, the builder uses the system's default charset ({@link Charset#defaultCharset()}). 073 * You can specify a custom charset using {@link #charset(Charset)} or {@link #charset(String)}. 074 * This is important when reading files that were written with a specific encoding. 075 * 076 * <h5 class='section'>See Also:</h5><ul> 077 * <li class='jc'>{@link FileWriterBuilder} - Builder for file writers 078 * <li class='jc'>{@link PathReaderBuilder} - Builder for Path-based readers 079 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a> 080 * </ul> 081 */ 082@SuppressWarnings({ 083 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 084}) 085public class FileReaderBuilder { 086 087 /** 088 * Creates a new builder. 089 * 090 * <h5 class='section'>Example:</h5> 091 * <p class='bjava'> 092 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 093 * .file(<js>"data.txt"</js>) 094 * .build(); 095 * </p> 096 * 097 * @return A new builder instance. 098 */ 099 public static FileReaderBuilder create() { 100 return new FileReaderBuilder(); 101 } 102 103 /** 104 * Creates a new builder initialized with the specified file. 105 * 106 * <h5 class='section'>Example:</h5> 107 * <p class='bjava'> 108 * File <jv>file</jv> = <jk>new</jk> File(<js>"config.properties"</js>); 109 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>(<jv>file</jv>) 110 * .charset(<js>"UTF-8"</js>) 111 * .build(); 112 * </p> 113 * 114 * @param file The file to read from. Can be <jk>null</jk> ({@link #build()} then throws an {@link IllegalArgumentException} unless {@link #allowNoFile()} is set). 115 * @return A new builder instance initialized with the specified file. 116 */ 117 public static FileReaderBuilder create(File file) { 118 return new FileReaderBuilder().file(file); 119 } 120 121 private File file; 122 123 private Charset cs = Charset.defaultCharset(); 124 125 private boolean allowNoFile; 126 127 /** 128 * Enables handling of missing files by returning an empty reader instead of throwing an exception. 129 * 130 * <p> 131 * When this option is enabled, if the file is <jk>null</jk> or does not exist, the {@link #build()} 132 * method will return a {@link StringReader} with empty content instead of throwing a 133 * {@link FileNotFoundException}. This is useful for optional configuration files. 134 * 135 * <h5 class='section'>Example:</h5> 136 * <p class='bjava'> 137 * <jc>// Without allowNoFile - throws exception if file doesn't exist</jc> 138 * Reader <jv>reader1</jv> = FileReaderBuilder.<jsm>create</jsm>() 139 * .file(<js>"required.txt"</js>) 140 * .build(); <jc>// Throws FileNotFoundException if file missing</jc> 141 * 142 * <jc>// With allowNoFile - returns empty reader if file doesn't exist</jc> 143 * Reader <jv>reader2</jv> = FileReaderBuilder.<jsm>create</jsm>() 144 * .file(<js>"optional.txt"</js>) 145 * .allowNoFile() 146 * .build(); <jc>// Returns empty StringReader if file missing</jc> 147 * </p> 148 * 149 * @return This object for method chaining. 150 */ 151 public FileReaderBuilder allowNoFile() { 152 return allowNoFile(true); 153 } 154 155 /** 156 * Enables or disables handling of missing files by returning an empty reader instead of throwing an exception. 157 * 158 * <p> 159 * Boolean-arg form of {@link #allowNoFile()}, useful when the setting is conditional. 160 * 161 * <h5 class='section'>Example:</h5> 162 * <p class='bjava'> 163 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 164 * .file(<js>"optional.txt"</js>) 165 * .allowNoFile(<jv>isOptional</jv>) 166 * .build(); 167 * </p> 168 * 169 * @param value <jk>true</jk> to return an empty reader when the file is missing. 170 * @return This object for method chaining. 171 */ 172 public FileReaderBuilder allowNoFile(boolean value) { 173 this.allowNoFile = value; 174 return this; 175 } 176 177 /** 178 * Creates a new {@link Reader} for reading from the configured file. 179 * 180 * <p> 181 * If {@link #allowNoFile()} was called and the file is <jk>null</jk> or does not exist, 182 * this method returns an empty {@link StringReader}. Otherwise, it creates an 183 * {@link InputStreamReader} with the specified character encoding. 184 * 185 * <h5 class='section'>Example:</h5> 186 * <p class='bjava'> 187 * <jk>try</jk> (Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 188 * .file(<js>"data.txt"</js>) 189 * .charset(<js>"UTF-8"</js>) 190 * .build()) { 191 * <jc>// Read from file</jc> 192 * } 193 * </p> 194 * 195 * @return A new {@link Reader} for reading from the file. 196 * @throws FileNotFoundException If the file could not be found and {@link #allowNoFile()} was not called. 197 */ 198 @SuppressWarnings({ 199 "resource" // Caller takes ownership of the returned Reader 200 }) 201 public Reader build() throws FileNotFoundException { 202 if (allowNoFile && (file == null || ! file.exists())) 203 return new StringReader(""); 204 reqnn("file", file); 205 return new InputStreamReader(new FileInputStream(file), cs != null ? cs : Charset.defaultCharset()); 206 } 207 208 /** 209 * Sets the character encoding for reading the file. 210 * 211 * <p> 212 * If not specified, the system's default charset ({@link Charset#defaultCharset()}) is used. 213 * Specifying the encoding is important when reading files that were written with a specific 214 * character encoding. 215 * 216 * <h5 class='section'>Example:</h5> 217 * <p class='bjava'> 218 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 219 * .file(<js>"data.txt"</js>) 220 * .charset(StandardCharsets.UTF_8) 221 * .build(); 222 * </p> 223 * 224 * @param cs The character encoding to use. Can be <jk>null</jk> to use the default charset ({@link Charset#defaultCharset()}). 225 * @return This object for method chaining. 226 */ 227 public FileReaderBuilder charset(Charset cs) { 228 this.cs = cs; 229 return this; 230 } 231 232 /** 233 * Sets the character encoding for reading the file by charset name. 234 * 235 * <p> 236 * This is a convenience method that accepts a charset name string and converts it to a 237 * {@link Charset} using {@link Charset#forName(String)}. 238 * 239 * <h5 class='section'>Example:</h5> 240 * <p class='bjava'> 241 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 242 * .file(<js>"data.txt"</js>) 243 * .charset(<js>"UTF-8"</js>) 244 * .build(); 245 * </p> 246 * 247 * @param cs The character encoding name (e.g., <js>"UTF-8"</js>, <js>"ISO-8859-1"</js>). 248 * The default is {@link Charset#defaultCharset()}. 249 * Must not be <jk>null</jk>. 250 * @return This object for method chaining. 251 */ 252 public FileReaderBuilder charset(String cs) { 253 this.cs = Charset.forName(reqnn("cs", cs)); 254 return this; 255 } 256 257 /** 258 * Sets the file to read from. 259 * 260 * <h5 class='section'>Example:</h5> 261 * <p class='bjava'> 262 * File <jv>f</jv> = <jk>new</jk> File(<js>"config.properties"</js>); 263 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 264 * .file(<jv>f</jv>) 265 * .build(); 266 * </p> 267 * 268 * @param value The file to read from. Can be <jk>null</jk> ({@link #build()} then throws an {@link IllegalArgumentException} unless {@link #allowNoFile()} is set). 269 * @return This object for method chaining. 270 */ 271 public FileReaderBuilder file(File value) { 272 file = value; 273 return this; 274 } 275 276 /** 277 * Sets the file path to read from. 278 * 279 * <h5 class='section'>Example:</h5> 280 * <p class='bjava'> 281 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 282 * .file(<js>"/path/to/file.txt"</js>) 283 * .build(); 284 * </p> 285 * 286 * @param path The file path to read from. Must not be <jk>null</jk>. 287 * @return This object for method chaining. 288 */ 289 public FileReaderBuilder file(String path) { 290 this.file = new File(path); 291 return this; 292 } 293 294 /** 295 * Sets the file to read from. 296 * 297 * <p> 298 * Convenience overload for callers working with the NIO {@link Path} API, consistent with 299 * {@link PathReaderBuilder#path(Path)}. 300 * 301 * <h5 class='section'>Example:</h5> 302 * <p class='bjava'> 303 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 304 * .file(Paths.<jsm>get</jsm>(<js>"/path/to/file.txt"</js>)) 305 * .build(); 306 * </p> 307 * 308 * @param value The file path to read from. Can be <jk>null</jk> ({@link #build()} then throws an {@link IllegalArgumentException} unless {@link #allowNoFile()} is set). 309 * @return This object for method chaining. 310 */ 311 public FileReaderBuilder file(Path value) { 312 this.file = value == null ? null : value.toFile(); 313 return this; 314 } 315}