changeset 199:ed38839a6b08

Ported over some WST parsing stuff from desktop flys flys-backend/trunk@1538 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Tue, 22 Mar 2011 15:48:09 +0000
parents d980e545ccab
children 88048d4f6e4d
files flys-backend/ChangeLog flys-backend/src/main/java/de/intevation/flys/App.java flys-backend/src/main/java/de/intevation/flys/importer/Importer.java flys-backend/src/main/java/de/intevation/flys/importer/WstParser.java flys-backend/src/main/java/de/intevation/flys/model/MainValueType.java flys-backend/src/main/java/de/intevation/flys/utils/StringUtil.java
diffstat 6 files changed, 1134 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/flys-backend/ChangeLog	Tue Mar 22 12:15:18 2011 +0000
+++ b/flys-backend/ChangeLog	Tue Mar 22 15:48:09 2011 +0000
@@ -1,3 +1,22 @@
+2011-03-22	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/importer/WstParser.java:
+	  Ported some stuff ver from WSTSource.java of desktop flys to
+	  parse WST files. TODO: create instances of the import models.
+
+	* src/main/java/de/intevation/flys/utils/StringUtil.java:
+	  Copied from desktop flys. Used for some string operations
+	  in WST parser.
+
+	* src/main/java/de/intevation/flys/importer/Importer.java:
+	  Added system property 'flys.backend.importer.dry.run'.
+	  Set to true only the parsing is done and no writing
+	  to the backend. Default: false.
+
+	*  src/main/java/de/intevation/flys/App.java,
+	   src/main/java/de/intevation/flys/model/MainValueType.java:
+	   Removed needless imports.
+
 2011-03-22  Ingo Weinzierl <ingo@intevation.de>
 
 	* src/main/java/de/intevation/flys/model/DischargeTableValue.java,
--- a/flys-backend/src/main/java/de/intevation/flys/App.java	Tue Mar 22 12:15:18 2011 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/App.java	Tue Mar 22 15:48:09 2011 +0000
@@ -2,8 +2,6 @@
 
 import org.hibernate.cfg.Configuration;
 
-import org.hibernate.SessionFactory;
-
 import de.intevation.flys.model.Annotation;
 import de.intevation.flys.model.Attribute;
 import de.intevation.flys.model.DischargeTable;
@@ -28,8 +26,6 @@
 
 import org.hibernate.cfg.Environment;
 
-import org.hibernate.Session;
-
 public class App 
 {
     private static final String USER =
--- a/flys-backend/src/main/java/de/intevation/flys/importer/Importer.java	Tue Mar 22 12:15:18 2011 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/importer/Importer.java	Tue Mar 22 15:48:09 2011 +0000
@@ -20,6 +20,9 @@
 {
     private static Logger log = Logger.getLogger(Importer.class);
 
+    public static final boolean DRY_RUN =
+        Boolean.getBoolean("flys.backend.importer.dry.run");
+
     public static final ThreadLocal<Session> sessionHolder =
         new ThreadLocal<Session>();
 
@@ -108,8 +111,9 @@
             }
         }
 
-        // TODO: Add a dry run option
-        new Importer(infoGewParser.getRivers()).writeToDatabase();
+        if (!DRY_RUN) {
+            new Importer(infoGewParser.getRivers()).writeToDatabase();
+        }
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-backend/src/main/java/de/intevation/flys/importer/WstParser.java	Tue Mar 22 12:15:18 2011 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/importer/WstParser.java	Tue Mar 22 15:48:09 2011 +0000
@@ -1,15 +1,336 @@
 package de.intevation.flys.importer;
 
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+
 import java.io.File;
 import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.InputStreamReader;
+import java.io.FileInputStream;
+
+import java.text.NumberFormat;
+
+import org.apache.log4j.Logger;
+
+import de.intevation.flys.utils.StringUtil;
 
 public class WstParser
 {
+    private static Logger log = Logger.getLogger(WstParser.class);
+
+    public static final String COLUMN_BEZ_TEXT   = "column-bez-text";
+    public static final String COLUMN_BEZ_BREITE = "column-bez-breite";
+    public static final String COLUMN_QUELLE     = "column-quelle";
+    public static final String COLUMN_DATUM      = "column-datum";
+
+    public static final Double UNDEFINED_ZERO = Double.valueOf(0.0);
+
+    public static final String ENCODING = "ISO-8859-1";
+
     public WstParser() {
     }
 
     public void parse(File file) throws IOException {
-        // TODO: Implement me!
+
+        log.info("Parsing WST file '" + file + "'");
+
+        LineNumberReader in = null;
+        try {
+            in =
+                new LineNumberReader(
+                new InputStreamReader(
+                new FileInputStream(file), ENCODING));
+
+            String input;
+            boolean first = true;
+            int columnCount = 0;
+
+            String [] lsHeader       = null;
+            String [] lsBezeichner   = null;
+            String [] langBezeichner = null;
+            int    [] colNaWidths    = null;
+            String [] quellen        = null;
+            String [] daten          = null;
+            double [] aktAbfluesse   = null;
+            double [] firstAbfluesse = null;
+
+            double minKm =  Double.MAX_VALUE;
+            double maxKm = -Double.MAX_VALUE;
+
+            boolean bFirstComment = true;
+            boolean columnHeaderChecked = false;
+
+            double lastKm = Double.MAX_VALUE;
+
+            String einheit = "Wassserstand [NN + m]";
+
+            HashMap<String, Double> oldEscapeLine = null;
+
+            while ((input = in.readLine()) != null) {
+                String line = input;
+                if (first) { // fetch number of columns
+                    if ((line = line.trim()).length() == 0) {
+                        continue;
+                    }
+                    try {
+                        columnCount = Integer.parseInt(line);
+                        if (columnCount <= 0) {
+                            throw new NumberFormatException(
+                                "number columns <= 0");
+                        }
+                        log.debug("Number of columns: " + columnCount);
+                        lsBezeichner = new String[columnCount];
+                        lsHeader     = new String[columnCount];
+                        aktAbfluesse = new double[columnCount];
+                    }
+                    catch (NumberFormatException nfe) {
+                        log.warn(nfe);
+                        continue;
+                    }
+                    first = false;
+                    continue;
+                }
+
+                line = line.replace(',', '.');
+
+                if (line.startsWith("*\u001f")) {
+                    Double [] data =
+                        parseLineAsDouble(line, columnCount, false, true);
+
+                    if (oldEscapeLine != null) {
+                        addInterval(minKm, maxKm, oldEscapeLine);
+                        minKm =  Double.MAX_VALUE;
+                        maxKm = -Double.MAX_VALUE;
+                    }
+
+                    oldEscapeLine = new HashMap<String, Double>();
+                    for (int i = 0; i < columnCount; ++i) {
+                        if (lsHeader[i] != null) {
+                            oldEscapeLine.put(lsHeader[i], data[i]);
+                        }
+                    }
+
+                    for (int i = Math.min(data.length, aktAbfluesse.length)-1;
+                         i >= 0; --i) {
+                        aktAbfluesse[i] = data[i].doubleValue();
+                    }
+
+                    if (firstAbfluesse == null) {
+                        firstAbfluesse = (double [])aktAbfluesse.clone();
+                    }
+                    continue;
+                }
+
+                if (line.startsWith("*!")) {
+                    String spezial = line.substring(2).trim();
+
+                    if (spezial.length() == 0) {
+                        continue;
+                    }
+
+                    if (spezial.startsWith(COLUMN_BEZ_TEXT)) {
+                        spezial = spezial.substring(COLUMN_BEZ_TEXT.length()).trim();
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+                        langBezeichner = StringUtil.splitQuoted(spezial, '"');
+                    }
+                    else if (spezial.startsWith(COLUMN_BEZ_BREITE)) {
+                        spezial = spezial.substring(COLUMN_BEZ_BREITE.length()).trim();
+
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+
+                        String[] split = spezial.split("\\s+");
+
+                        colNaWidths = new int[split.length];
+                        for (int i=0; i < split.length; i++) {
+                            colNaWidths[i] = Integer.parseInt(split[i]);
+                        }
+                    }
+                    else if (spezial.startsWith(COLUMN_QUELLE)) {
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+                        quellen = StringUtil.splitQuoted(spezial, '"');
+                    }
+                    else if (spezial.startsWith(COLUMN_DATUM)) {
+                        spezial = spezial.substring(COLUMN_DATUM.length()).trim();
+                        if (spezial.length() == 0) {
+                            continue;
+                        }
+                        daten = StringUtil.splitQuoted(spezial, '"');
+                    }
+                    continue;
+                }
+
+                if (line.startsWith("*")) {
+                    if (bFirstComment && line.length() >= 11) {
+                        String yAxis = line.substring(10).trim();
+                        if (yAxis.length() > 0) {
+                            einheit = yAxis;
+                        }
+                        bFirstComment = false;
+                    }
+                    continue;
+                }
+
+                if (firstAbfluesse != null) {
+                    if (!columnHeaderChecked) {
+                        int unknownCount = 0;
+                        for (int i = 0; i < lsHeader.length; ++i) {
+                            if (lsBezeichner[i] == null
+                            || lsBezeichner[i].length() == 0) {
+                                double q = firstAbfluesse[i];
+                                if (q < 0.001) {
+                                    lsBezeichner[i] =
+                                        "<unbekannt#" + unknownCount + ">";
+                                    ++unknownCount;
+                                }
+                                else {
+                                    lsBezeichner[i] = "Q="+format(q);
+                                }
+                            }
+                            lsHeader[i] = lsBezeichner[i] + " " + einheit;
+                        }
+                        columnHeaderChecked = true;
+                    }
+
+                    Double [] data =
+                        parseLineAsDouble(line, columnCount, true, false);
+
+                    double kaem = data[0];
+
+                    if (kaem < minKm) {
+                        minKm = kaem;
+                    }
+                    if (kaem > maxKm) {
+                        maxKm = kaem;
+                    }
+
+                    lastKm = kaem;
+
+                    // extract values
+                    for (int i = 0; i < columnCount; ++i) {
+                        addValue(kaem, data[i].doubleValue(), lsBezeichner[i]);
+                    }
+
+                }
+                else { // firstAbfluesse == null
+                    if (langBezeichner != null) {
+                        lsBezeichner = StringUtil.fitArray(
+                            langBezeichner, lsBezeichner);
+                    }
+                    else if (colNaWidths != null) {
+                        for (int j = 0, i = 0, N = input.length();
+                             j < colNaWidths.length && i < N;
+                             i += colNaWidths[j++]
+                        ) {
+                            lsBezeichner[j] = input.substring(
+                                i, i+colNaWidths[j]).trim();
+                        }
+                    }
+                    else {
+                        // first column begins at position 8 in line
+                        for (int i = 8, col = 0; i < input.length(); i += 9) {
+                            if ((i + 9) > input.length()) {
+                                i = input.length() - 10;
+                            }
+                            // one column header is 9 chars wide
+                            lsBezeichner[col++] =
+                                input.substring(i, i + 9).trim();
+
+                            if (col == lsBezeichner.length) {
+                                break;
+                            }
+                        }
+                    }
+                }
+
+            }
+            addInterval(minKm, maxKm, oldEscapeLine);
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    protected void addValue(double km, double w, String columnName) {
+        // TODO: store me!
+    }
+
+    protected static String format(double value) {
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setMinimumFractionDigits(2);
+        nf.setMaximumFractionDigits(2);
+        return nf.format(value);
+    }
+
+    protected void addInterval(
+        double              from,
+        double              to, 
+        Map<String, Double> values
+    ) {
+        log.debug("addInterval: " + from + " " + to);
+        if (values == null) {
+            return;
+        }
+    }
+
+    private static final Double [] parseLineAsDouble(
+        String  line, 
+        int     count, 
+        boolean bStation,
+        boolean bParseEmptyAsZero
+    ) {
+        String [] tokens = parseLine(line, count, bStation);
+
+        Double [] doubles = new Double[tokens.length];
+
+        for (int i = 0; i < doubles.length; ++i) {
+            String token = tokens[i].trim();
+            if (token.length() != 0) {
+                doubles[i] = Double.valueOf(token);
+            }
+            else if (bParseEmptyAsZero) {
+                doubles[i] = UNDEFINED_ZERO;
+            }
+        }
+
+        return doubles;
+    }
+
+    private static String [] parseLine(
+        String  line, 
+        int     tokenCount,
+        boolean bParseStation
+    ) {
+        ArrayList<String> strings = new ArrayList<String>();
+
+        if (bParseStation) {
+            if (line.length() < 8) {
+                throw new IllegalArgumentException("station too short");
+            }
+            strings.add(line.substring(0, 8));
+        }
+
+        int pos = 9;
+        for (int i = 0; i < tokenCount; ++i) {
+            if (line.length() >= pos + 8) {
+                strings.add(line.substring(pos, pos + 8));
+            }
+            else {
+                strings.add("");
+            }
+            pos += 9;
+        }
+
+        return strings.toArray(new String[strings.size()]);
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-backend/src/main/java/de/intevation/flys/model/MainValueType.java	Tue Mar 22 12:15:18 2011 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/model/MainValueType.java	Tue Mar 22 15:48:09 2011 +0000
@@ -1,7 +1,5 @@
 package de.intevation.flys.model;
 
-import java.math.BigDecimal;
-
 import java.io.Serializable;
 
 import javax.persistence.Entity;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-backend/src/main/java/de/intevation/flys/utils/StringUtil.java	Tue Mar 22 15:48:09 2011 +0000
@@ -0,0 +1,787 @@
+package de.intevation.flys.utils;
+
+/**
+ * Copyright (c) 2006 by Intevation GmbH
+ *
+ * @author Sascha L. Teichmann (teichmann@intevation.de)
+ * @author Ludwig Reiter       (ludwig@intevation.de)
+ *
+ * This program is free software under the LGPL (&gt;=v2.1)
+ * Read the file LGPL coming with FLYS for details.
+ */
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import java.net.URLEncoder;
+import java.net.URLDecoder;
+
+import java.io.UnsupportedEncodingException;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+
+public final class StringUtil {
+    final static String NUMBER_SEPERATOR = ";";
+    final static String LINE_SEPERATOR = ":";
+
+    private StringUtil() {
+    }
+
+    public static final String double2DArrayToString(double[][] values) {
+
+        if (values == null) {
+            throw new IllegalArgumentException("keine double[][]-Werte");
+        }
+
+        StringBuilder strbuf = new StringBuilder();
+
+        for (int i=0; i < values.length; i++) {
+            if (i>0) {
+                strbuf.append(LINE_SEPERATOR);
+            }
+            for (int j=0; j < values[i].length; j++) {
+                if (j > 0) {
+                    strbuf.append(NUMBER_SEPERATOR);
+                }
+                strbuf.append(values[i][j]);
+            }
+        }
+
+        return strbuf.toString();
+    }
+
+    public static final double[][] stringToDouble2DArray(String str) {
+        if (str == null || str.length() == 0) {
+            return null;
+        }
+
+        String[] lineSplit = str.split(LINE_SEPERATOR);
+        double[][] array2D = new double[lineSplit.length][];
+        for (int i=0; i < lineSplit.length; i++) {
+            String[] numberSplit =  lineSplit[i].split(NUMBER_SEPERATOR);
+
+            double[] numbers = new double[numberSplit.length];
+            for (int j=0; j < numberSplit.length; j++) {
+                numbers[j] = Double.valueOf(numberSplit[j]).doubleValue();
+            }
+
+            array2D[i] = numbers;
+        }
+
+        return array2D;
+    }
+
+    public static final String [] splitLines(String s) {
+        if (s == null) {
+            return null;
+        }
+        ArrayList<String> list = new ArrayList<String>();
+
+        BufferedReader in = null;
+
+        try {
+            in =
+                new BufferedReader(
+                new StringReader(s));
+
+            String line;
+
+            while ((line = in.readLine()) != null) {
+                list.add(line);
+            }
+        }
+        catch (IOException ioe) {
+            return null;
+        }
+        finally {
+            if (in != null)
+                try {
+                    in.close();
+                }
+                catch (IOException ioe) {}
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
+
+    public static final String concat(String [] s) {
+        return concat(s, null);
+    }
+
+    public static final String concat(String [] s, String glue) {
+        if (s == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length; ++i) {
+            if (i > 0 && glue != null) {
+                sb.append(glue);
+            }
+            sb.append(s[i]);
+        }
+        return sb.toString();
+    }
+
+    public static final String [] splitAfter(String [] src, int N) {
+        if (src == null) {
+            return null;
+        }
+
+        ArrayList<String> list = new ArrayList<String>(src.length);
+        for (int i = 0; i < src.length; ++i) {
+            String s = src[i];
+            int R;
+            if (s == null || (R = s.length()) == 0) {
+                list.add(s);
+            }
+            else {
+                while (R > N) {
+                    list.add(s.substring(0, N));
+                    s = s.substring(N);
+                    R = s.length();
+                }
+                list.add(s);
+            }
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
+    public static final String [] splitQuoted(String s) {
+        return splitQuoted(s, '"');
+    }
+
+    public static final String[] fitArray(String [] src, String [] dst) {
+        if (src == null) {
+            return dst;
+        }
+        if (dst == null) {
+            return src;
+        }
+
+        if (src.length == dst.length) {
+            return src;
+        }
+
+        System.arraycopy(src, 0, dst, 0, Math.min(dst.length, src.length));
+
+        return dst;
+    }
+
+    public static final String [] splitQuoted(String s, char quoteChar) {
+        if (s == null) {
+            return null;
+        }
+        ArrayList<String> l = new ArrayList<String>();
+        int mode = 0, last_mode = 0;
+        StringBuilder sb = new StringBuilder();
+        for (int N = s.length(), i = 0; i < N; ++i) {
+            char c = s.charAt(i);
+            switch (mode) {
+                case 0: // unquoted mode
+                    if (c == quoteChar) {
+                        mode = 1; // to quoted mode
+                        if (sb.length() > 0) {
+                            l.add(sb.toString());
+                            sb.setLength(0);
+                        }
+                    }
+                    else if (c == '\\') {
+                        last_mode = 0;
+                        mode = 2; // escape mode
+                    }
+                    else if (!Character.isWhitespace(c)) {
+                        sb.append(c);
+                    }
+                    else if (sb.length() > 0) {
+                        l.add(sb.toString());
+                        sb.setLength(0);
+                    }
+                    break;
+                case 1: // quote mode
+                    if (c == '\\') {
+                        last_mode = 1;
+                        mode = 2; // escape mode
+                    }
+                    else if (c == quoteChar) { // leave quote mode
+                        l.add(sb.toString());
+                        sb.setLength(0);
+                        mode = 0; // to unquoted mode
+                    }
+                    else {
+                        sb.append(c);
+                    }
+                    break;
+                case 2: // escape mode
+                    sb.append(c);
+                    mode = last_mode;
+                    break;
+            }
+        }
+        if (sb.length() > 0) {
+            l.add(sb.toString());
+        }
+        return l.toArray(new String[l.size()]);
+    }
+
+    public static final String [] splitUnique(String s) {
+        return splitUnique(s, "[\\s,]+");
+    }
+
+    public static final String [] splitUnique(String s, String sep) {
+        return s != null ? unique(s.split(sep)) : null;
+    }
+
+    public static final String [] unique(String [] str) {
+        if (str == null || str.length == 1) {
+            return str;
+        }
+
+        Arrays.sort(str);
+
+        for (int i = 1; i < str.length; ++i)
+            if (str[i].equals(str[i-1])) {
+                ArrayList<String> list = new ArrayList<String>(str.length);
+
+                for (int j = 0; j < i; ++j) {
+                    list.add(str[j]);
+                }
+
+                String last = str[i];
+
+                for (++i; i < str.length; ++i)
+                    if (!last.equals(str[i])) {
+                        list.add(last = str[i]);
+                    }
+
+                return list.toArray(new String[list.size()]);
+            }
+
+        return str;
+    }
+
+    public static final String [] ensureEmptyExistence(String [] str) {
+        if (str == null) {
+            return null;
+        }
+
+        for (int i = 0; i < str.length; ++i)
+            if (str[i].length() == 0) {
+                if (i != 0) { // copy to front
+                    String t = str[0];
+                    str[0] = str[i];
+                    str[i] = t;
+                }
+                return str;
+            }
+
+        String [] n = new String[str.length+1];
+        n[0] = "";
+        System.arraycopy(str, 0, n, 1, str.length);
+        return n;
+    }
+
+    public static final String ensureWidthPadLeft(String s, int width, char pad) {
+        int N = s.length();
+        if (N >= width) {
+            return s;
+        }
+        StringBuilder sb = new StringBuilder(width);
+        for (; N < width; ++N) {
+            sb.append(pad);
+        }
+        sb.append(s);
+        return sb.toString();
+    }
+
+    public static final String [] splitWhiteSpaceWithNAsPad(
+        String s,
+        int    N,
+        String pad
+    ) {
+        if (s == null) {
+            return null;
+        }
+
+        boolean copyChars = true;
+        int     count     = 0; // number of WS
+
+        int S = s.length();
+
+        ArrayList<String> parts = new ArrayList<String>();
+
+        StringBuilder part = new StringBuilder(S);
+
+        for (int i = 0; i < S; ++i) {
+            char c = s.charAt(i);
+            if (copyChars) { // char mode
+                if (Character.isWhitespace(c)) {
+                    if (part.length() > 0) {
+                        parts.add(part.toString());
+                        part.setLength(0);
+                    }
+                    count     = 1;
+                    copyChars = false; // to WS mode
+                }
+                else {
+                    part.append(c);
+                }
+            }
+            else { // counting WS
+                if (Character.isWhitespace(c)) {
+                    ++count;
+                }
+                else {
+                    while (count >= N) {// enough to insert pad?
+                        parts.add(pad);
+                        count -= N;
+                    }
+                    part.append(c);
+                    count     = 0;
+                    copyChars = true; // back to char mode
+                }
+            }
+        } // for all chars
+
+        if (copyChars) {
+            if (part.length() > 0) {
+                parts.add(part.toString());
+            }
+        }
+        else {
+            while (count >= N) { // enough to insert pad?
+                parts.add(pad);
+                count -= N;
+            }
+        }
+
+        return parts.toArray(new String[parts.size()]);
+    }
+
+    public static final String encodeURL(String url) {
+        try {
+            return url != null
+                   ? URLEncoder.encode(url, "UTF-8")
+                   : "";
+        }
+        catch (UnsupportedEncodingException usee) {
+            throw new RuntimeException(usee.getLocalizedMessage());
+        }
+    }
+
+    public static final String decodeURL(String url) {
+        try {
+            return url != null
+                   ? URLDecoder.decode(url, "UTF-8")
+                   : "";
+        }
+        catch (UnsupportedEncodingException usee) {
+            throw new RuntimeException(usee.getLocalizedMessage());
+        }
+    }
+
+    public static final boolean isEmpty(String s) {
+        return s == null || s.length() == 0;
+    }
+
+    public static final String empty(String s) {
+        return s == null ? "" : s;
+    }
+
+
+    public static final String trim(String s) {
+        return s != null ? s.trim() : null;
+    }
+
+    public static final String uniqueWhitespaces(String s) {
+        if (s == null) {
+            return null;
+        }
+
+        boolean wasWS = false;
+        StringBuilder sb = new StringBuilder();
+
+        for (int N = s.length(), i = 0; i < N; ++i) {
+            char c = s.charAt(i);
+            if (Character.isWhitespace(c)) {
+                if (!wasWS) {
+                    sb.append(c);
+                    wasWS = true;
+                }
+            }
+            else {
+                sb.append(c);
+                wasWS = false;
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public static final String replaceNewlines(String s) {
+        return s == null
+               ? null
+               : s.replace('\r', ' ').replace('\n', ' ');
+    }
+
+    /*
+    public static final String quoteReplacement(String s) {
+
+        if (s == null || (s.indexOf('\\') == -1 && s.indexOf('$') == -1))
+            return s;
+
+        StringBuilder sb = new StringBuilder();
+
+        for (int N = s.length(), i = 0; i < N; ++i) {
+            char c = s.charAt(i);
+            if (c == '\\' || c == '$') sb.append('\\');
+            sb.append(c);
+        }
+
+        return sb.toString();
+    }
+    */
+
+    public static final String quoteReplacement(String s) {
+
+        if (s == null) {
+            return null;
+        }
+
+        for (int N = s.length(), i = 0; i < N; ++i) { // plain check loop
+            char c = s.charAt(i);
+            if (c == '$' || c == '\\') { // first special -> StringBuilder
+                StringBuilder sb = new StringBuilder(s.substring(0, i))
+                .append('\\')
+                .append(c);
+                for (++i; i < N; ++i) { // build StringBuilder with rest
+                    if ((c = s.charAt(i)) == '$' || c == '\\') {
+                        sb.append('\\');
+                    }
+                    sb.append(c);
+                }
+                return sb.toString();
+            }
+        }
+
+        return s;
+    }
+
+    public static final String repeat(String what, int times) {
+        return repeat(what, times, new StringBuilder()).toString();
+    }
+
+    public static final StringBuilder repeat(String what, int times, StringBuilder sb) {
+        while (times-- > 0) {
+            sb.append(what);
+        }
+        return sb;
+    }
+
+    /**
+     * Gibt den Dateinamen in S ohne Dateiendung zurück.
+     */
+    public static final String cutExtension(String s) {
+        if (s == null) {
+            return null;
+        }
+        int dot = s.lastIndexOf('.');
+        return dot >= 0
+               ? s.substring(0, dot)
+               : s;
+    }
+
+    public static final String extension(String s) {
+        if (s == null) {
+            return null;
+        }
+        int dot = s.lastIndexOf('.');
+        return dot >= 0
+               ? s.substring(dot+1)
+               : s;
+    }
+
+    public static final String [] splitExtension(String x) {
+        if (x == null) {
+            return null;
+        }
+        int i = x.lastIndexOf('.');
+        return i < 0
+               ? new String[] { x, null }
+               : new String[] { x.substring(0, Math.max(0, i)), x.substring(i+1).toLowerCase() };
+    }
+
+    public static String entityEncode(String s) {
+        if (s == null || s.length() == 0) {
+            return s;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i=0, N =s.length(); i < N; i++) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '<':
+                    sb.append("&lt;");
+                    break;
+                case '>':
+                    sb.append("&gt;");
+                    break;
+                case '&':
+                    sb.append("&amp;");
+                    break;
+                default:
+                    sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String entityDecode(String s) {
+        if (s == null || s.length() == 0) {
+            return s;
+        }
+
+        boolean amp = false;
+        StringBuilder sb = new StringBuilder();
+        StringBuilder ampbuf = new StringBuilder();
+        for (int i=0, N =s.length(); i < N; i++) {
+            char c = s.charAt(i);
+            if (amp) {
+                if (c == ';') {
+                    amp = false;
+                    String str = ampbuf.toString();
+                    ampbuf.setLength(0);
+                    if (str.equals("lt")) {
+                        sb.append('<');
+                    }
+                    else if (str.equals("gt")) {
+                        sb.append('>');
+                    }
+                    else if (str.equals("amp")) {
+                        sb.append('&');
+                    }
+                    else {
+                        sb.append('&').append(str).append(';');
+                    }
+                }
+                else {
+                    ampbuf.append(c);
+                }
+            }
+            else if (c=='&') {
+                amp = true;
+            }
+            else {
+                sb.append(c);
+            }
+
+        }
+        return sb.toString();
+    }
+
+    public static final String quote(String s) {
+        return quote(s, '"');
+    }
+
+    public static final String quote(String s, char quoteChar) {
+        if (s == null) {
+            return null;
+        }
+
+        int N = s.length();
+
+        if (N == 0)
+            return new StringBuilder(2)
+                   .append(quoteChar)
+                   .append(quoteChar)
+                   .toString();
+
+        StringBuilder sb = null;
+
+        int i = 0;
+
+        for (; i < N; ++i) {
+            char c = s.charAt(i);
+
+            if (Character.isWhitespace(c)) {
+                sb = new StringBuilder()
+                .append(quoteChar)
+                .append(s.substring(0, i+1));
+                break;
+            }
+            else if (c == quoteChar) {
+                sb = new StringBuilder()
+                .append(quoteChar)
+                .append(s.substring(0, i))
+                .append('\\')
+                .append(quoteChar);
+                break;
+            }
+        }
+
+        if (sb == null) {
+            return s;
+        }
+
+        for (++i; i < N; ++i) {
+            char c = s.charAt(i);
+            if (c == quoteChar || c == '\\') {
+                sb.append('\\');
+            }
+
+            sb.append(c);
+        }
+
+        return sb.append(quoteChar).toString();
+    }
+
+    /*
+    public static String sprintf(String format, Object... args) {
+        return sprintf(null, format, args);
+    }
+    */
+
+    public static String sprintf(Locale locale, String format, Object ... args) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        pw.printf(locale, format, args);
+        pw.flush();
+        return sw.toString();
+    }
+
+
+    public static void testQuote() {
+        System.err.println("testing quote:");
+
+        String cases []  = {
+            "",          "''",
+            "test",      "test",
+            "test test", "'test test'",
+            "  test",    "'  test'",
+            "test   ",   "'test   '",
+            " test ",    "' test '",
+            "'test",     "'\\'test'",
+            "'",         "'\\''",
+            " ' ' ",     "' \\' \\' '",
+            "te'st",     "'te\\'st'"
+        };
+
+        int failed = 0;
+
+        for (int i = 0; i < cases.length; i += 2) {
+            String in  = cases[i];
+            String out = cases[i+1];
+
+            String res = quote(in, '\'');
+            if (!res.equals(out)) {
+                ++failed;
+                System.err.println(
+                    "quote failed on: >" + in +
+                    "< result: >" + res +
+                    "< expected: >" + out + "<");
+            }
+        }
+
+        int T = cases.length/2;
+
+        System.err.println("tests total: "  + T);
+        System.err.println("tests failed: " + failed);
+        System.err.println("tests passed: " + (T - failed));
+    }
+
+    public static void testQuoteReplacement() {
+        System.err.println("testing quoteReplacement:");
+
+        String cases []  = {
+            "",          "",
+            "test",      "test",
+            "$",         "\\$",
+            "\\",        "\\\\",
+            "\\$",       "\\\\\\$",
+            "test\\$",   "test\\\\\\$",
+            "\\test",    "\\\\test",
+            "test$",     "test\\$",
+            "test$test", "test\\$test",
+            "$test$",    "\\$test\\$"
+        };
+
+        int failed = 0;
+
+        for (int i = 0; i < cases.length; i += 2) {
+            String in  = cases[i];
+            String out = cases[i+1];
+
+            String res = quoteReplacement(in);
+            if (!res.equals(out)) {
+                ++failed;
+                System.err.println(
+                    "quoteReplacement failed on: '" + in +
+                    "' result: '" + res +
+                    "' expected: '" + out + "'");
+            }
+        }
+
+        int T = cases.length/2;
+
+        System.err.println("tests total: "  + T);
+        System.err.println("tests failed: " + failed);
+        System.err.println("tests passed: " + (T - failed));
+    }
+
+    public static void testStringArray2D() {
+        int total = 0;
+        int fail = 0;
+        int passed = 0;
+
+        System.err.println("testing StringArray2D:");
+
+        double[][] testarray = {{1.0, 2.0, 3.0},
+            {1.1, 2.1, 3.1},
+            {100.2, 200.2}
+        };
+        String str = double2DArrayToString(testarray);
+
+        total += 1;
+        if (str.equals("1.0;2.0;3.0:1.1;2.1;3.1:100.2;200.2")) {
+            passed +=1;
+        }
+        else {
+            fail +=1;
+            System.err.println("Der Ergebnis-String ist nicht richtig:");
+            System.err.println(str);
+        }
+
+
+
+        double[][] testarray2 = stringToDouble2DArray(str);
+        boolean failed = false;
+
+        total +=1;
+        for (int i=0; i < testarray.length; i++)
+            for (int j=0; j < testarray[i].length; j++)
+                if (testarray[i][j] != testarray2[i][j]) {
+                    System.err.println("Test scheitert bei i=" +i +" j=" +j);
+                    System.err.println("alter Wert=" + testarray[i][j] +" neuer Wert=" +testarray2[i][j]);
+                    failed = true;
+                }
+        if (failed) {
+            fail +=1;
+        }
+        else {
+            passed +=1;
+        }
+        System.err.println("tests total: "+ total);
+        System.err.println("tests failed: "+ fail);
+        System.err.println("tests passed: "+ passed);
+    }
+
+    public static void main(String [] args) {
+
+        testQuoteReplacement();
+        testQuote();
+        testStringArray2D();
+    }
+}
+// end of file

http://dive4elements.wald.intevation.org