001/*
002 * Copyright (c) 2012-2018 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.File;
014import java.io.IOException;
015import java.io.OutputStream;
016import java.io.PrintStream;
017import java.net.MalformedURLException;
018import java.net.URI;
019import java.net.URL;
020import java.nio.charset.Charset;
021import java.nio.file.Files;
022import java.nio.file.Path;
023import java.nio.file.Paths;
024import java.nio.file.StandardOpenOption;
025
026import static gololang.Predefined.require;
027
028/**
029 * Module providing IO related utility functions.
030 */
031public final class IO {
032  private IO() {
033    // utility class
034  }
035
036  /**
037   * Returns the default charset.
038   */
039  public static Charset defaultCharset() {
040    return Charset.defaultCharset();
041  }
042
043  /**
044   * Convert the given String or Charset object into a Charset.
045   */
046  public static Charset toCharset(Object encoding) {
047    if (encoding == null) {
048      return defaultCharset();
049    }
050    Charset charset = null;
051    if (encoding instanceof String) {
052      charset = Charset.forName((String) encoding);
053    } else if (encoding instanceof Charset) {
054      charset = (Charset) encoding;
055    } else {
056      throw new IllegalArgumentException("Can't get a charset from a "
057          + encoding.getClass().getName());
058    }
059    return charset;
060  }
061
062  /**
063   * Reads the content of a text file.
064   *
065   * @param file     the file to read from as an instance of either {@link String}, {@link File} or {@link Path}.
066   * @param encoding the file encoding as a {@link String} or {@link Charset}.
067   * @return the content as a {@link String}.
068   */
069  public static String fileToText(Object file, Object encoding) throws Throwable {
070    return new String(Files.readAllBytes(toPath(file)), toCharset(encoding));
071  }
072
073  /**
074   * Polymorphic {@link java.nio.file.Path} creation.
075   *
076   * @param file the file descriptor as an instance of either {@link String}, {@link File} or {@link Path}.
077   * @return the corresponding {@link java.nio.file.Path} object.
078   */
079  public static Path toPath(Object file) {
080    if (file == null) {
081      return null;
082    } else if (file instanceof String) {
083      return Paths.get((String) file);
084    } else if (file instanceof File) {
085      return ((File) file).toPath();
086    } else if (file instanceof Path) {
087      return (Path) file;
088    }
089    throw new IllegalArgumentException("file must be a string, a file or a path");
090  }
091
092  /**
093   * Polymorphic {@link java.net.URL} creation.
094   *
095   * @param ref the url descriptor as a {@link String}, {@link URL}, {@link URI}, {@link File} or {@link Path}.
096   * @return the corresponding {@link java.net.URL} object.
097   */
098  public static URL toURL(Object ref) throws MalformedURLException {
099    if (ref == null) {
100      return null;
101    } else if (ref instanceof String) {
102      return new URL((String) ref);
103    } else if (ref instanceof URL) {
104      return (URL) ref;
105    } else if (ref instanceof URI) {
106      return ((URI) ref).toURL();
107    } else if (ref instanceof Path) {
108      return ((Path) ref).toUri().toURL();
109    } else if (ref instanceof File) {
110      return ((File) ref).toURI().toURL();
111    }
112    throw new IllegalArgumentException(String.format("Can't convert a %s into a URL", ref.getClass().getName()));
113  }
114
115
116  /**
117   * Writes some text to a file.
118   *
119   * 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.
120   *
121   * @param text the text to write.
122   * @param file the file to write to as an instance of either {@link String}, {@link File} or {@link Path}.
123   */
124  public static void textToFile(Object text, Object file) throws Throwable {
125    textToFile(text, file, null);
126  }
127
128  /**
129   * Writes some text to a file using the given {@link Charset}.
130   *
131   * 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.
132   *
133   * @param text the text to write.
134   * @param file the file to write to as an instance of either {@link String}, {@link File} or {@link Path}.
135   * @param charset the charset to encode the text in.
136   */
137  public static void textToFile(Object text, Object file, Object charset) throws Throwable {
138    require(text instanceof String, "text must be a string");
139    Charset encoding = toCharset(charset);
140    String str = (String) text;
141    if ("-".equals(file.toString())) {
142      System.out.write(str.getBytes(encoding));
143    } else {
144      Path path = toPath(file);
145      if (path.getParent() != null) {
146        Files.createDirectories(path.getParent());
147      }
148      Files.write(
149          path,
150          str.getBytes(encoding),
151          StandardOpenOption.WRITE,
152          StandardOpenOption.CREATE,
153          StandardOpenOption.TRUNCATE_EXISTING);
154    }
155  }
156
157  /**
158   * Check if a file exists.
159   *
160   * @param file the file to read from as an instance of either {@link String}, {@link File} or {@link Path}.
161   * @return true if the file exists, false if it doesn't
162   */
163  public static boolean fileExists(Object file) {
164    return Files.exists(toPath(file));
165  }
166
167  /**
168   * Reads the next line of characters from the console.
169   *
170   * @return a String.
171   */
172  public static String readln() throws IOException {
173    return System.console().readLine();
174  }
175
176  /**
177   * Reads the next line of characters from the console.
178   *
179   * @param message displays a prompt message.
180   * @return a String.
181   */
182  public static String readln(String message) throws IOException {
183    System.out.print(message);
184    return readln();
185  }
186
187  /**
188   * Reads a password from the console with echoing disabled.
189   *
190   * @return a String.
191   */
192  public static String readPassword() throws IOException {
193    return String.valueOf(System.console().readPassword());
194  }
195
196  /**
197   * Reads a password from the console with echoing disabled.
198   *
199   * @param message displays a prompt message.
200   * @return a String.
201   */
202  public static String readPassword(String message) throws IOException {
203    System.out.print(message);
204    return readPassword();
205  }
206
207  /**
208   * Reads a password from the console with echoing disabled, returning an {@code char[]} array.
209   *
210   * @return a character array.
211   */
212  public static char[] secureReadPassword() throws IOException {
213    return System.console().readPassword();
214  }
215
216  /**
217   * Reads a password from the console with echoing disabled, returning an {@code char[]} array.
218   *
219   * @param message displays a prompt message.
220   * @return a character array.
221   */
222  public static char[] secureReadPassword(String message) throws IOException {
223    System.out.print(message);
224    return secureReadPassword();
225  }
226
227  /**
228   * Create an {@code PrintStream} from the specified value.
229   * <p>
230   * Same as {@code printStreamFrom(output, defaultCharset())}
231   *
232   * @param output the file to use; "-" means standard output
233   * @return a buffered {@code PrintStream} or {@code java.lang.System.out}
234   * @see #defaultCharset()
235   * @see #printStreamFrom(Object, Object)
236   */
237  public static PrintStream printStreamFrom(Object output) throws IOException {
238    return printStreamFrom(output, defaultCharset());
239  }
240
241  /**
242   * Create an {@code PrintStream} from the specified value.
243   * <p>
244   * If the given string is "-", {@code java.lang.System.out} is used. Otherwise, a {@code java.nio.file.Path} is
245   * created with {@link #toPath(Object)}.
246   * The returned {@code PrintStream} is buffered and uses the given charset. Parent directory is created. If the file
247   * exists, it is overwritten.
248   *
249   * @param output the file to use; "-" means standard output
250   * @param charset the charset to use, as a {@code java.lang.String} or a {@code java.nio.charset.Charset}
251   * @return a buffered {@code PrintStream} or {@code java.lang.System.out}
252   * @see #toPath(Object)
253   * @see #toCharset(Object)
254   */
255  public static PrintStream printStreamFrom(Object output, Object charset) throws IOException {
256    if ("-".equals(output)) {
257      return System.out;
258    }
259    if (output instanceof PrintStream) {
260      return (PrintStream) output;
261    }
262    OutputStream out;
263    if (output instanceof OutputStream) {
264      out = (OutputStream) output;
265    } else {
266      Path outputPath = toPath(output);
267      if (outputPath.getParent() != null) {
268        Files.createDirectories(outputPath.getParent());
269      }
270      out = Files.newOutputStream(outputPath);
271    }
272    return new PrintStream(
273        new BufferedOutputStream(out),
274        true,
275        toCharset(charset).name());
276  }
277
278
279}