diff --git a/src/main/java/schule/ngb/zm/util/io/FileLoader.java b/src/main/java/schule/ngb/zm/util/io/FileLoader.java index f049f71..ac353a0 100644 --- a/src/main/java/schule/ngb/zm/util/io/FileLoader.java +++ b/src/main/java/schule/ngb/zm/util/io/FileLoader.java @@ -2,12 +2,14 @@ package schule.ngb.zm.util.io; import schule.ngb.zm.util.Log; +import java.io.BufferedReader; import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -23,19 +25,24 @@ public final class FileLoader { public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; - public static List loadLines( String source ) { return loadLines(source, UTF8); } public static List loadLines( String source, Charset charset ) { - try { - return Files.readAllLines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset); - } catch( IOException | URISyntaxException ex ) { - LOG.error(ex, "Error while loading lines from source <%s>", source); - } + try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) { + List result = new ArrayList<>(); - return Collections.emptyList(); + String line; + while( (line = reader.readLine()) != null ) { + result.add(line); + } + + return result; + } catch( IOException ex ) { + LOG.error(ex, "Error while loading lines from source <%s>", source); + return Collections.emptyList(); + } } public static String loadText( String source ) { @@ -43,61 +50,94 @@ public final class FileLoader { } public static String loadText( String source, Charset charset ) { - try { - return Files.readString(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset); - } catch( IOException | URISyntaxException ex ) { - LOG.error(ex, "Error while loading text from source <%s>", source); - } + try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) { + StringBuilder result = new StringBuilder(); - return ""; + String line; + while( (line = reader.readLine()) != null ) { + result.append(line).append('\n'); + } + + return result.toString(); + } catch( IOException ex ) { + LOG.error(ex, "Error while loading string from source <%s>", source); + return ""; + } } public static String[][] loadCsv( String source, boolean skipFirst ) { return loadCsv(source, ',', skipFirst, UTF8); } + /** + * Lädt die Daten aus einer CSV Datei in ein zweidimensionales + * String-Array. + *

+ * Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten. + * Insbesondere können Inhalte, die das Trennzeichen {@code separator} + * enthalten, nicht korrekt erkannt werden. Das Trennzeichen wird unabhängig + * vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das + * Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art + * {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.) + *

+ * Es wird auch keine erweiterte Inhaltserkennung ausgeführt, sondern alle + * Inhalte als {@code String} gelesen. Die weitere Verarbeitung mit den + * passenden Parser-Methoden (beispielsweise + * {@link Double#parseDouble(String)}) obligt dem Nutzer. + * + * @param source Die Quelle der CSV-Daten. + * @param separator Das verwendete Trennzeichen. + * @param skipFirst Ob die erste Zeile übersprungen werden soll. + * @param charset Die zu verwendende Zeichenkodierung. + * @return Ein Array mit den Daten als {@code String}s. + */ public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) { - try( Stream lines = Files - .lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset) - ) { - int n = skipFirst ? 1 : 0; - return lines.skip(n).map( - ( line ) -> line.split(Character.toString(separator)) - ).toArray(String[][]::new); - } catch( IOException | URISyntaxException ex ) { - LOG.error(ex, "Error while loading csv source <%s>", source); - } - - return new String[0][0]; + int n = skipFirst ? 1 : 0; + List lines = loadLines(source, charset); + return lines.stream().skip(n).map( + //( line ) -> line.split(Character.toString(separator)) + ( line ) -> line.split("\\s*" + separator + "\\s*") + ).toArray(String[][]::new); } public static double[][] loadValues( String source, char separator, boolean skipFirst ) { return loadValues(source, separator, skipFirst, UTF8); } + /** + * Lädt Double-Werte aus einer CSV Datei in ein zweidimensionales Array. + *

+ * Die gelesenen Strings werden mit {@link Double#parseDouble(String)} in + * {@code double} umgeformt. Es leigt in der Verantwortung des Nutzers + * sicherzustellen, dass die CSV-Datei auch nur Zahlen enthält, die korrekt + * in {@code double} umgewandelt werden können. Zellen für die die + * Umwandlung fehlschlägt werden mit 0.0 befüllt. + *

+ * Die Methode unterliegt denselben Einschränkungen wie + * {@link #loadCsv(String, char, boolean, Charset)}. + * + * @param source Die Quelle der CSV-Daten. + * @param separator Das verwendete Trennzeichen. + * @param skipFirst Ob die erste Zeile übersprungen werden soll. + * @param charset Die zu verwendende Zeichenkodierung. + * @return Ein Array mit den Daten als {@code String}s. + */ public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) { - try( Stream lines = Files - .lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset) - ) { - int n = skipFirst ? 1 : 0; - return lines.skip(n).map( - ( line ) -> Arrays - .stream(line.split(Character.toString(separator))) - .mapToDouble( - ( value ) -> { - try { - return Double.parseDouble(value); - } catch( NumberFormatException nfe ) { - return 0.0; - } + int n = skipFirst ? 1 : 0; + List lines = loadLines(source, charset); + return lines.stream().skip(n).map( + ( line ) -> Arrays + .stream(line.split(Character.toString(separator))) + .mapToDouble( + ( value ) -> { + try { + return Double.parseDouble(value); + } catch( NumberFormatException nfe ) { + return 0.0; } - ).toArray() - ).toArray(double[][]::new); - } catch( IOException | URISyntaxException ex ) { - LOG.error(ex, "Error while loading double values from csv source <%s>", source); - } - - return new double[0][0]; + } + ).toArray() + ).toArray(double[][]::new); } public FileLoader() { diff --git a/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java b/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java index 6fb053b..c0087da 100644 --- a/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java +++ b/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java @@ -5,13 +5,66 @@ import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Validator; import java.io.*; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; /** * Helferklasse, um {@link InputStream}s für Resourcen zu erhalten. */ public class ResourceStreamProvider { + + /** + * Ermittelt zur angegebenen Quelle einen passenden {@link URL} (Unified + * Resource Locator). Eine passende Datei-Resource wird wie folgt + * ermittelt: + *

    + *
  1. Ist {@code source} eine existierende Datei + * ({@code new File(source}.isFile() == true})?
  2. + *
  3. Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.
  4. + *
  5. Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?
  6. + *
  7. Ansonten erstellt ein {@link URL}-Objekt.
  8. + *
+ *

+ * Ein {@code URL} für die erste gefundene Resource wird zurückgegeben. + * Auftretende Exceptions + * werden als {@link IOException} geworfen. + *

+ * Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt. + * Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL} + * zu einer Datei verhindert daher, dass noch im Classpath gesucht wird. + * + * @param source Eine Quelle für die Resource (Absoluter Dateipfad, + * Dateipfad im Classpath oder Netzwerkresource) + * @return Ein {@code InputStream} für die Resource + * @throws NullPointerException Falls {@code source} {@code null} ist. + * @throws IllegalArgumentException Falls {@code source} ein leerer String + * ist. + * @throws IOException Geworfen beim Erzeugen einer URL zu + * einer bestehenden Resource. + */ + public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException { + Validator.requireNotNull(source, "Resource source may not be null"); + Validator.requireNotEmpty(source, "Resource source may not be empty."); + + // Ist source ein valider Dateipfad? + File file = new File(source); + if( file.isFile() ) { + return file.toURI().toURL(); + } + // Ist source im Classpath vorhanden? + URL url = Zeichenmaschine.class.getClassLoader().getResource(source); + if( url != null ) { + return url; + } + // Dann versuchen aus source direkt eine URL zu machen. + return new URL(source); + } + /** * Sucht eine zur angegebenen Quelle passende Resource und öffnet einen * passenden {@link InputStream}. Die konkrete Art des Streams hängt davon @@ -45,134 +98,41 @@ public class ResourceStreamProvider { * @throws NullPointerException Falls {@code source} {@code null} ist. * @throws IllegalArgumentException Falls {@code source} ein leerer String * ist. - * @throws IOException Geworfen beim öffnen des Streams zu + * @throws IOException Geworfen beim Öffnen des Streams zu * einer bestehenden Resource oder falls * keine passende Resource gefunden wurde. */ public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException { - Validator.requireNotNull(source, "Resource source may not be null"); - Validator.requireNotEmpty(source, "Resource source may not be empty."); - - InputStream in = null; - - // See if source is a readable file - File file = new File(source); - try { - if( file.isFile() ) { - in = new FileInputStream(file); - } - } catch( FileNotFoundException fnfex ) { - // Somehow an exception occurred, but we still try other sources - } - // File does not exist, try other means - // load ressource relative to .class-file - if( in == null ) { - in = Zeichenmaschine.class.getResourceAsStream(source); - } - - // relative to ClassLoader - if( in == null ) { - in = Zeichenmaschine.class.getClassLoader().getResourceAsStream(source); - } - - // load form web or jar-file - if( in == null ) { - in = new URL(source).openStream(); - } - - // One of the above got a valid Stream, - // otherwise an Exception was thrown - return in; - } - - public static InputStream getInputStream( File file ) throws IOException { - Validator.requireNotNull(file, "Provided file can't be null."); - return new FileInputStream(file); - } - - public static InputStream getInputStream( URL url ) throws IOException { - Validator.requireNotNull(url, "Provided URL can't be null."); - return url.openStream(); + return getResourceURL(source).openStream(); } /** - * Ermittelt zur angegebenen Quelle einen passenden {@link URL} (Unified - * Resource Locator). Eine passende Datei-Resource wird wie folgt - * ermittelt: - *

    - *
  1. Ist {@code source} eine existierende Datei - * ({@code new File(source}.isFile() == true})?
  2. - *
  3. Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.
  4. - *
  5. Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?
  6. - *
  7. Ansonten erstellt ein {@link URL}-Objekt.
  8. - *
- *

- * Ein {@code URL} für die erste gefundene Resource wird zurückgegeben. - * Auftretende Exceptions - * werden als {@link IOException} geworfen. - *

- * Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt. - * Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL} - * zu einer Datei verhindert daher, dass noch im Classpath gesucht wird. - * - * @param source Eine Quelle für die Resource (Absoluter Dateipfad, - * Dateipfad im Classpath oder Netzwerkresource) - * @return Ein {@code InputStream} für die Resource - * @throws NullPointerException Falls {@code source} {@code null} ist. - * @throws IllegalArgumentException Falls {@code source} ein leerer String - * ist. - * @throws IOException Geworfen beim erzeugen eines URL zu - * einer bestehenden Resource. - */ - public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException { - Validator.requireNotNull(source, "Resource source may not be null"); - Validator.requireNotEmpty(source, "Resource source may not be empty."); - - File file = new File(source); - if( file.isFile() ) { - return file.toURI().toURL(); - } - - URL url; - - url = Zeichenmaschine.class.getResource(source); - if( url != null ) { - return url; - } - - url = Zeichenmaschine.class.getClassLoader().getResource(source); - if( url != null ) { - return url; - } - - return new URL(source); - } - - /** - * Ver - * * @param source * @return * @throws IOException */ public static OutputStream getOutputStream( String source ) throws IOException { - Validator.requireNotNull(source, "Resource source may not be null"); - Validator.requireNotEmpty(source, "Resource source may not be empty."); - URL url = getResourceURL(source); - return getOutputStream(new File(url.getPath())); + try { + return Files.newOutputStream(Path.of(getResourceURL(source).toURI())); + } catch( URISyntaxException ex ) { + throw new IOException(ex); + } } - public static OutputStream getOutputStream( File file ) throws IOException { - Validator.requireNotNull(file, "Provided file can't be null."); - return new FileOutputStream(file); + public static BufferedReader getReader( String source ) throws IOException { + return getReader(source, StandardCharsets.UTF_8); } - public static Reader getReader( String source ) throws IOException { - return new InputStreamReader(getInputStream(source)); + public static BufferedReader getReader( String source, Charset charset ) throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder())); } - public static Writer getWriter( String source ) throws IOException { - return new OutputStreamWriter(getOutputStream(source)); + public static BufferedWriter getWriter( String source ) throws IOException { + return getWriter(source, StandardCharsets.UTF_8); + } + + public static BufferedWriter getWriter( String source, Charset charset ) throws IOException { + return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder())); } private ResourceStreamProvider() {