001/* 002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010package gololang; 011 012import java.io.BufferedOutputStream; 013import java.io.BufferedReader; 014import java.io.File; 015import java.io.IOException; 016import java.io.OutputStream; 017import java.io.PrintStream; 018import java.net.MalformedURLException; 019import java.net.URI; 020import java.net.URL; 021import java.nio.charset.Charset; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.nio.file.Paths; 025import java.nio.file.StandardOpenOption; 026import java.util.Iterator; 027import java.util.NoSuchElementException; 028 029import static gololang.Predefined.require; 030 031/** 032 * Module providing IO related utility functions. 033 */ 034public final class IO { 035 private IO() { 036 // utility class 037 } 038 039 /** 040 * An iterator on the lines of a {@code java.io.BufferedReader}. 041 */ 042 public static final class LinesIterator implements Iterator<String> { 043 private final BufferedReader reader; 044 private String currentLine; 045 046 public static Iterator<String> of(Object source) throws IOException { 047 require(source instanceof BufferedReader, "The source must be a reader"); 048 return new LinesIterator((BufferedReader) source); 049 } 050 051 private LinesIterator(BufferedReader reader) throws IOException { 052 require(reader != null, "The reader must not be null"); 053 this.reader = reader; 054 this.currentLine = reader.readLine(); 055 } 056 057 @Override 058 public boolean hasNext() { 059 return this.currentLine != null; 060 } 061 062 @Override 063 public String next() { 064 if (this.currentLine == null) { 065 throw new NoSuchElementException(); 066 } 067 String val = this.currentLine; 068 try { 069 this.currentLine = this.reader.readLine(); 070 } catch (IOException e) { 071 this.currentLine = null; 072 } 073 return val; 074 } 075 } 076 077 /** 078 * Opens a file for reading. 079 * 080 * @param file the file to read from as an instance of either {@link String}, {@link File} or {@link Path}. 081 * @return a {@code java.io.BufferedReader}. 082 */ 083 public static BufferedReader openFile(Object file) throws IOException { 084 return Files.newBufferedReader(toPath(file)); 085 } 086 087 /** 088 * Returns the default charset. 089 */ 090 public static Charset defaultCharset() { 091 return Charset.defaultCharset(); 092 } 093 094 /** 095 * Converts the given String or Charset object into a Charset. 096 */ 097 public static Charset toCharset(Object encoding) { 098 if (encoding == null) { 099 return defaultCharset(); 100 } 101 Charset charset = null; 102 if (encoding instanceof String) { 103 charset = Charset.forName((String) encoding); 104 } else if (encoding instanceof Charset) { 105 charset = (Charset) encoding; 106 } else { 107 throw new IllegalArgumentException("Can't get a charset from a " 108 + encoding.getClass().getName()); 109 } 110 return charset; 111 } 112 113 /** 114 * Reads the content of a text file. 115 * 116 * @param file the file to read from as an instance of either {@link String}, {@link File} or {@link Path}. 117 * @param encoding the file encoding as a {@link String} or {@link Charset}. 118 * @return the content as a {@link String}. 119 */ 120 public static String fileToText(Object file, Object encoding) throws Throwable { 121 return new String(Files.readAllBytes(toPath(file)), toCharset(encoding)); 122 } 123 124 /** 125 * Polymorphic {@link java.nio.file.Path} creation. 126 * 127 * @param file the file descriptor as an instance of either {@link String}, {@link File} or {@link Path}. 128 * @return the corresponding {@link java.nio.file.Path} object. 129 */ 130 public static Path toPath(Object file) { 131 if (file == null) { 132 return null; 133 } else if (file instanceof String) { 134 return Paths.get((String) file); 135 } else if (file instanceof File) { 136 return ((File) file).toPath(); 137 } else if (file instanceof Path) { 138 return (Path) file; 139 } 140 throw new IllegalArgumentException("file must be a string, a file or a path"); 141 } 142 143 /** 144 * Polymorphic {@link java.net.URL} creation. 145 * 146 * @param ref the url descriptor as a {@link String}, {@link URL}, {@link URI}, {@link File} or {@link Path}. 147 * @return the corresponding {@link java.net.URL} object. 148 */ 149 public static URL toURL(Object ref) throws MalformedURLException { 150 if (ref == null) { 151 return null; 152 } else if (ref instanceof String) { 153 return new URL((String) ref); 154 } else if (ref instanceof URL) { 155 return (URL) ref; 156 } else if (ref instanceof URI) { 157 return ((URI) ref).toURL(); 158 } else if (ref instanceof Path) { 159 return ((Path) ref).toUri().toURL(); 160 } else if (ref instanceof File) { 161 return ((File) ref).toURI().toURL(); 162 } 163 throw new IllegalArgumentException(String.format("Can't convert a %s into a URL", ref.getClass().getName())); 164 } 165 166 167 /** 168 * Writes some text to a file. 169 * 170 * The file and parents directories are created if they does not exist. The file is overwritten if it already exists. If the file is {@code "-"}, the content is written to standard output. 171 * 172 * @param text the text to write. 173 * @param file the file to write to as an instance of either {@link String}, {@link File} or {@link Path}. 174 */ 175 public static void textToFile(Object text, Object file) throws Throwable { 176 textToFile(text, file, null); 177 } 178 179 /** 180 * Writes some text to a file using the given {@link Charset}. 181 * 182 * The file and parents directories are created if they does not exist. The file is overwritten if it already exists. If the file is {@code "-"}, the content is written to standard output. 183 * 184 * @param text the text to write. 185 * @param file the file to write to as an instance of either {@link String}, {@link File} or {@link Path}. 186 * @param charset the charset to encode the text in. 187 */ 188 public static void textToFile(Object text, Object file, Object charset) throws Throwable { 189 require(text instanceof String, "text must be a string"); 190 Charset encoding = toCharset(charset); 191 String str = (String) text; 192 if ("-".equals(file.toString())) { 193 System.out.write(str.getBytes(encoding)); 194 } else { 195 Path path = toPath(file); 196 if (path.getParent() != null) { 197 Files.createDirectories(path.getParent()); 198 } 199 Files.write( 200 path, 201 str.getBytes(encoding), 202 StandardOpenOption.WRITE, 203 StandardOpenOption.CREATE, 204 StandardOpenOption.TRUNCATE_EXISTING); 205 } 206 } 207 208 /** 209 * Check if a file exists. 210 * 211 * @param file the file to read from as an instance of either {@link String}, {@link File} or {@link Path}. 212 * @return true if the file exists, false if it doesn't 213 */ 214 public static boolean fileExists(Object file) { 215 return Files.exists(toPath(file)); 216 } 217 218 /** 219 * Reads the next line of characters from the console. 220 * 221 * @return a String. 222 */ 223 public static String readln() throws IOException { 224 return System.console().readLine(); 225 } 226 227 /** 228 * Reads the next line of characters from the console. 229 * 230 * @param message displays a prompt message. 231 * @return a String. 232 */ 233 public static String readln(String message) throws IOException { 234 System.out.print(message); 235 return readln(); 236 } 237 238 /** 239 * Reads a password from the console with echoing disabled. 240 * 241 * @return a String. 242 */ 243 public static String readPassword() throws IOException { 244 return String.valueOf(System.console().readPassword()); 245 } 246 247 /** 248 * Reads a password from the console with echoing disabled. 249 * 250 * @param message displays a prompt message. 251 * @return a String. 252 */ 253 public static String readPassword(String message) throws IOException { 254 System.out.print(message); 255 return readPassword(); 256 } 257 258 /** 259 * Reads a password from the console with echoing disabled, returning an {@code char[]} array. 260 * 261 * @return a character array. 262 */ 263 public static char[] secureReadPassword() throws IOException { 264 return System.console().readPassword(); 265 } 266 267 /** 268 * Reads a password from the console with echoing disabled, returning an {@code char[]} array. 269 * 270 * @param message displays a prompt message. 271 * @return a character array. 272 */ 273 public static char[] secureReadPassword(String message) throws IOException { 274 System.out.print(message); 275 return secureReadPassword(); 276 } 277 278 /** 279 * Create an {@code PrintStream} from the specified value. 280 * <p> 281 * Same as {@code printStreamFrom(output, defaultCharset())} 282 * 283 * @param output the file to use; "-" means standard output 284 * @return a buffered {@code PrintStream} or {@code java.lang.System.out} 285 * @see #defaultCharset() 286 * @see #printStreamFrom(Object, Object) 287 */ 288 public static PrintStream printStreamFrom(Object output) throws IOException { 289 return printStreamFrom(output, defaultCharset()); 290 } 291 292 /** 293 * Create an {@code PrintStream} from the specified value. 294 * <p> 295 * If the given string is "-", {@code java.lang.System.out} is used. Otherwise, a {@code java.nio.file.Path} is 296 * created with {@link #toPath(Object)}. 297 * The returned {@code PrintStream} is buffered and uses the given charset. Parent directory is created. If the file 298 * exists, it is overwritten. 299 * 300 * @param output the file to use; "-" means standard output 301 * @param charset the charset to use, as a {@code java.lang.String} or a {@code java.nio.charset.Charset} 302 * @return a buffered {@code PrintStream} or {@code java.lang.System.out} 303 * @see #toPath(Object) 304 * @see #toCharset(Object) 305 */ 306 public static PrintStream printStreamFrom(Object output, Object charset) throws IOException { 307 if ("-".equals(output)) { 308 return System.out; 309 } 310 if (output instanceof PrintStream) { 311 return (PrintStream) output; 312 } 313 OutputStream out; 314 if (output instanceof OutputStream) { 315 out = (OutputStream) output; 316 } else { 317 Path outputPath = toPath(output); 318 if (outputPath.getParent() != null) { 319 Files.createDirectories(outputPath.getParent()); 320 } 321 out = Files.newOutputStream(outputPath); 322 } 323 return new PrintStream( 324 new BufferedOutputStream(out), 325 true, 326 toCharset(charset).name()); 327 } 328 329 330}