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 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020 021import java.io.*; 022import java.nio.charset.*; 023 024/** 025 * A fluent builder for creating {@link Reader} instances from files with configurable character encoding. 026 * 027 * <p> 028 * This builder provides a convenient way to create file readers with custom character encodings 029 * and optional handling for missing files. It's particularly useful when you need to read files 030 * with specific encodings or handle cases where files may not exist. 031 * 032 * <h5 class='section'>Features:</h5> 033 * <ul class='spaced-list'> 034 * <li>Fluent API - all methods return <c>this</c> for method chaining 035 * <li>Character encoding support - specify custom charset for file reading 036 * <li>Missing file handling - optional support for returning empty reader when file doesn't exist 037 * <li>Multiple file specification methods - accept File, String path, or Path 038 * </ul> 039 * 040 * <h5 class='section'>Use Cases:</h5> 041 * <ul class='spaced-list'> 042 * <li>Reading files with specific character encodings (UTF-8, ISO-8859-1, etc.) 043 * <li>Handling optional configuration files that may not exist 044 * <li>Creating readers with consistent encoding across an application 045 * <li>Reading files where encoding must be explicitly specified 046 * </ul> 047 * 048 * <h5 class='section'>Usage:</h5> 049 * <p class='bjava'> 050 * <jc>// Basic usage</jc> 051 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 052 * .file(<js>"/path/to/file.txt"</js>) 053 * .charset(<js>"UTF-8"</js>) 054 * .build(); 055 * 056 * <jc>// With missing file handling</jc> 057 * Reader <jv>reader2</jv> = FileReaderBuilder.<jsm>create</jsm>() 058 * .file(<js>"optional-config.properties"</js>) 059 * .allowNoFile() 060 * .build(); <jc>// Returns empty StringReader if file doesn't exist</jc> 061 * 062 * <jc>// Using File object</jc> 063 * File <jv>f</jv> = <jk>new</jk> File(<js>"data.txt"</js>); 064 * Reader <jv>reader3</jv> = FileReaderBuilder.<jsm>create</jsm>(<jv>f</jv>) 065 * .charset(StandardCharsets.UTF_8) 066 * .build(); 067 * </p> 068 * 069 * <h5 class='section'>Character Encoding:</h5> 070 * <p> 071 * By default, the builder uses the system's default charset ({@link Charset#defaultCharset()}). 072 * You can specify a custom charset using {@link #charset(Charset)} or {@link #charset(String)}. 073 * This is important when reading files that were written with a specific encoding. 074 * 075 * <h5 class='section'>See Also:</h5><ul> 076 * <li class='jc'>{@link FileWriterBuilder} - Builder for file writers 077 * <li class='jc'>{@link PathReaderBuilder} - Builder for Path-based readers 078 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a> 079 * </ul> 080 */ 081public class FileReaderBuilder { 082 083 /** 084 * Creates a new builder. 085 * 086 * <h5 class='section'>Example:</h5> 087 * <p class='bjava'> 088 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 089 * .file(<js>"data.txt"</js>) 090 * .build(); 091 * </p> 092 * 093 * @return A new builder instance. 094 */ 095 public static FileReaderBuilder create() { 096 return new FileReaderBuilder(); 097 } 098 099 /** 100 * Creates a new builder initialized with the specified file. 101 * 102 * <h5 class='section'>Example:</h5> 103 * <p class='bjava'> 104 * File <jv>file</jv> = <jk>new</jk> File(<js>"config.properties"</js>); 105 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>(<jv>file</jv>) 106 * .charset(<js>"UTF-8"</js>) 107 * .build(); 108 * </p> 109 * 110 * @param file The file to read from. 111 * @return A new builder instance initialized with the specified file. 112 */ 113 public static FileReaderBuilder create(File file) { 114 return new FileReaderBuilder().file(file); 115 } 116 117 private File file; 118 119 private Charset cs = Charset.defaultCharset(); 120 121 private boolean allowNoFile; 122 123 /** 124 * Enables handling of missing files by returning an empty reader instead of throwing an exception. 125 * 126 * <p> 127 * When this option is enabled, if the file is <jk>null</jk> or does not exist, the {@link #build()} 128 * method will return a {@link StringReader} with empty content instead of throwing a 129 * {@link FileNotFoundException}. This is useful for optional configuration files. 130 * 131 * <h5 class='section'>Example:</h5> 132 * <p class='bjava'> 133 * <jc>// Without allowNoFile - throws exception if file doesn't exist</jc> 134 * Reader <jv>reader1</jv> = FileReaderBuilder.<jsm>create</jsm>() 135 * .file(<js>"required.txt"</js>) 136 * .build(); <jc>// Throws FileNotFoundException if file missing</jc> 137 * 138 * <jc>// With allowNoFile - returns empty reader if file doesn't exist</jc> 139 * Reader <jv>reader2</jv> = FileReaderBuilder.<jsm>create</jsm>() 140 * .file(<js>"optional.txt"</js>) 141 * .allowNoFile() 142 * .build(); <jc>// Returns empty StringReader if file missing</jc> 143 * </p> 144 * 145 * @return This object for method chaining. 146 */ 147 public FileReaderBuilder allowNoFile() { 148 this.allowNoFile = true; 149 return this; 150 } 151 152 /** 153 * Creates a new {@link Reader} for reading from the configured file. 154 * 155 * <p> 156 * If {@link #allowNoFile()} was called and the file is <jk>null</jk> or does not exist, 157 * this method returns an empty {@link StringReader}. Otherwise, it creates an 158 * {@link InputStreamReader} with the specified character encoding. 159 * 160 * <h5 class='section'>Example:</h5> 161 * <p class='bjava'> 162 * <jk>try</jk> (Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 163 * .file(<js>"data.txt"</js>) 164 * .charset(<js>"UTF-8"</js>) 165 * .build()) { 166 * <jc>// Read from file</jc> 167 * } 168 * </p> 169 * 170 * @return A new {@link Reader} for reading from the file. 171 * @throws FileNotFoundException If the file could not be found and {@link #allowNoFile()} was not called. 172 */ 173 public Reader build() throws FileNotFoundException { 174 if (allowNoFile && (file == null || ! file.exists())) 175 return new StringReader(""); 176 assertArgNotNull("file", file); 177 return new InputStreamReader(new FileInputStream(file), cs != null ? cs : Charset.defaultCharset()); 178 } 179 180 /** 181 * Sets the character encoding for reading the file. 182 * 183 * <p> 184 * If not specified, the system's default charset ({@link Charset#defaultCharset()}) is used. 185 * Specifying the encoding is important when reading files that were written with a specific 186 * character encoding. 187 * 188 * <h5 class='section'>Example:</h5> 189 * <p class='bjava'> 190 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 191 * .file(<js>"data.txt"</js>) 192 * .charset(StandardCharsets.UTF_8) 193 * .build(); 194 * </p> 195 * 196 * @param cs The character encoding to use. The default is {@link Charset#defaultCharset()}. 197 * @return This object for method chaining. 198 */ 199 public FileReaderBuilder charset(Charset cs) { 200 this.cs = cs; 201 return this; 202 } 203 204 /** 205 * Sets the character encoding for reading the file by charset name. 206 * 207 * <p> 208 * This is a convenience method that accepts a charset name string and converts it to a 209 * {@link Charset} using {@link Charset#forName(String)}. 210 * 211 * <h5 class='section'>Example:</h5> 212 * <p class='bjava'> 213 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 214 * .file(<js>"data.txt"</js>) 215 * .charset(<js>"UTF-8"</js>) 216 * .build(); 217 * </p> 218 * 219 * @param cs The character encoding name (e.g., <js>"UTF-8"</js>, <js>"ISO-8859-1"</js>). 220 * The default is {@link Charset#defaultCharset()}. 221 * Must not be <jk>null</jk>. 222 * @return This object for method chaining. 223 */ 224 public FileReaderBuilder charset(String cs) { 225 this.cs = Charset.forName(assertArgNotNull("cs", cs)); 226 return this; 227 } 228 229 /** 230 * Sets the file to read from. 231 * 232 * <h5 class='section'>Example:</h5> 233 * <p class='bjava'> 234 * File <jv>f</jv> = <jk>new</jk> File(<js>"config.properties"</js>); 235 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 236 * .file(<jv>f</jv>) 237 * .build(); 238 * </p> 239 * 240 * @param value The file to read from. 241 * @return This object for method chaining. 242 */ 243 public FileReaderBuilder file(File value) { 244 file = value; 245 return this; 246 } 247 248 /** 249 * Sets the file path to read from. 250 * 251 * <h5 class='section'>Example:</h5> 252 * <p class='bjava'> 253 * Reader <jv>reader</jv> = FileReaderBuilder.<jsm>create</jsm>() 254 * .file(<js>"/path/to/file.txt"</js>) 255 * .build(); 256 * </p> 257 * 258 * @param path The file path to read from. 259 * @return This object for method chaining. 260 */ 261 public FileReaderBuilder file(String path) { 262 this.file = new File(path); 263 return this; 264 } 265}