ch.rgw.tools.StringTool.java Source code

Java tutorial

Introduction

Here is the source code for ch.rgw.tools.StringTool.java

Source

/*******************************************************************************
 * Copyright (c) 2005-2011, G. Weirich and Elexis
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    G. Weirich - initial implementation
 *    
 *******************************************************************************/

package ch.rgw.tools;

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.compress.bzip2.CBZip2InputStream;
import org.apache.commons.compress.bzip2.CBZip2OutputStream;

import ch.rgw.compress.CompEx;
import ch.rgw.compress.GLZInputStream;
import ch.rgw.compress.GLZOutputStream;
import ch.rgw.compress.HuffmanInputStream;
import ch.rgw.compress.HuffmanOutputStream;
import ch.rgw.compress.HuffmanTree;
import ch.rgw.tools.net.NetTool;

/**
 * Einige Hilfsfunktionen mit und an Strings und String-Collections
 * 
 * @author Gerry Weirich
 */

public class StringTool {

    public static final String Version() {
        return "2.0.4";
    }

    private static String default_charset = "utf-8";
    public static final String leer = "";
    public static final String space = " ";
    public static final String equals = "=";
    public static final String crlf = "\r\n";
    public static final String lf = "\n";
    public static final String slash = "/";
    public static final String backslash = "\\";

    public static final String numbers = "[0-9]+";
    public static final String wordSeparatorChars = "\n\r\t.,;:!? ";
    public static final String wordSeparators = "[\\t ,\\.:\\?!\\n\\r]";
    public static final String lineSeparators = "[\\n\\r\\.\\?!;]";
    public static final String ipv4address = "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}";
    public static final String ipv6address = "((([0-9a-f]{1,4}+:){7}+[0-9a-f]{1,4}+)|(:(:[0-9a-f]{1,4}+){1,6}+)|(([0-9a-f]{1,4}+:){1,6}+:)|(::)|(([0-9a-f]{1,4}+:)(:[0-9a-f]{1,4}+){1,5}+)|(([0-9a-f]{1,4}+:){1,2}+(:[0-9a-f]{1,4}+){1,4}+)|(([0-9a-f]{1,4}+:){1,3}+(:[0-9a-f]{1,4}+){1,3}+)|(([0-9a-f]{1,4}+:){1,4}+(:[0-9a-f]{1,4}+){1,2}+)|(([0-9a-f]{1,4}+:){1,5}+(:[0-9a-f]{1,4}+))|(((([0-9a-f]{1,4}+:)?([0-9a-f]{1,4}+:)?([0-9a-f]{1,4}+:)?([0-9a-f]{1,4}+:)?)|:)(:(([0-9]{1,3}+\\.){3}+[0-9]{1,3}+)))|(:(:[0-9a-f]{1,4}+)*:([0-9]{1,3}+\\.){3}+[0-9]{1,3}+))(/[0-9]+)?";

    // public static final String wordChars="a-zA-Z\'";
    public static final String wordChars = "\\p{L}\'";
    private static int ipHash;
    private static long sequence;
    public static final int LEFT = 1;
    public static final int RIGHTS = 2;

    /**
     * Set the charset to use in all charset-dependent String operations
     * 
     * @param charset_name
     *            the name of the charset (that must be valid)
     */
    public static void setDefaultCharset(String charset_name) {
        default_charset = charset_name;
    }

    /**
     * get the configured default charset
     * 
     * @return the charset name (defaults to utf-8)
     */
    public static String getDefaultCharset() {
        return default_charset;
    }

    /**
     * create a String from a byte array, using the configured charset (defaults to utf-8)
     * 
     * @param bytes
     *            an array of bytes rthat constitute a String in the indicated charset.
     * @return the created String.
     */
    public static String createString(byte[] bytes) {
        try {
            return new String(bytes, default_charset);
        } catch (UnsupportedEncodingException e) {
            // should not happen
            ExHandler.handle(e);
        }
        return null;
    }

    /**
     * Create a byte arra from a String using the configured charset (defaults to utf-8)
     * 
     * @param string
     * @return
     */
    public static byte[] getBytes(String string) {
        try {
            return string.getBytes(default_charset);
        } catch (UnsupportedEncodingException e) {
            // should not happen
            ExHandler.handle(e);
        }
        return null;
    }

    /**
     * return the bounds of a Rectangle around a String
     * 
     * @deprecated this ist a dependency to Swing
     */
    @Deprecated
    public static Rectangle2D getStringBounds(final String s, final Graphics g) {
        if (isNothing(s)) {
            return new Rectangle(0, 0);
        }
        FontRenderContext frc = ((Graphics2D) g).getFontRenderContext();
        Font fnt = g.getFont();
        Rectangle2D r = fnt.getStringBounds(s, frc);
        return r;
    }

    /**
     * Split a String into a String Arry
     * 
     * @deprecated obsoleted by java 1.4x 's {@link String#split(String) String.split} method.
     */
    @Deprecated
    @SuppressWarnings("unchecked")
    public static String[] split(final String m, final String delim) {
        Vector v = splitV(m, delim);
        if (v == null) {
            return null;
        }
        String[] ret = (String[]) v.toArray(new String[1]);
        return ret;
    }

    /**
     * Spaltet einen String in einen Vektor
     * 
     * @param m
     *            der zu splittende String
     * @param delim
     *            Trennzeichen, an dem zu splitten ist.
     */

    @SuppressWarnings("unchecked")
    public static Vector splitV(final String m, final String delim) {
        String mi = m;
        if (mi.equals("")) {
            return null;
        }
        Vector v = new Vector(30, 30);

        int i = 0, j = 0;
        while (true) {
            j = mi.indexOf(delim, i);
            if (j == -1) {
                v.add(mi.substring(i));
                break;
            }
            String l = mi.substring(i, j).trim();
            if (!l.equals("")) {
                v.add(l);
            }
            i = j + 1;
        }

        return v;
    }

    /**
     * Split a String into an ArrayList
     * 
     * @param m
     *            the String to msplit
     * @param delim
     *            the delimiter to split at
     * @return an ArrayList containing at least one element without the delimiter
     */
    @SuppressWarnings("unchecked")
    public static List<String> splitAL(final String m, final String delim) {
        ArrayList al = new ArrayList();
        String mi = m;
        int i = 0, j = 0;
        while (true) {
            j = mi.indexOf(delim, i);
            if (j == -1) {
                al.add(mi.substring(i));
                break;
            }
            String l = mi.substring(i, j).trim();
            if (!l.equals("")) {
                al.add(l);
            }
            i = j + 1;
        }
        return al;
    }

    public static final String flattenSeparator = "~#<";

    /**
     * Wandelt eine Hashtable in einen String aus Kommagetrennten a=b-Paaren um.
     */

    @SuppressWarnings("unchecked")
    public static String flattenStrings(final Hashtable h) {
        return flattenStrings(h, null);
    }

    public static String flattenStrings(final Hashtable<Object, Object> h, final flattenFilter fil) {
        if (h == null) {
            return null;
        }
        Enumeration<Object> keys = h.keys();
        StringBuffer res = new StringBuffer(1000);
        res.append("FS1").append(flattenSeparator);
        while (keys.hasMoreElements()) {
            Object ko = (keys.nextElement());
            if (fil != null) {
                if (fil.accept(ko) == false) {
                    continue;
                }
            }
            String v = ObjectToString(h.get(ko));
            String k = ObjectToString(ko);
            if ((k == null) || (v == null) || k.matches(".*=.*")) { // log.log("attempt
                // to
                // flatten
                // unsupported
                // object
                // type",Log.FATALS);
                return null;
            }
            res.append(k).append("=").append(v).append(flattenSeparator);
        }
        String r = res.toString();
        return r.replaceFirst(flattenSeparator + "$", "");
    }

    public static final int NONE = 0;
    public static final int HUFF = 1;
    public static final int BZIP = 2;
    public static final int GLZ = 3;
    public static final int ZIP = 4;
    public static final int GUESS = 99;

    /**
     * Eine String-Collection comprimieren
     * 
     * @param strings
     * @param compressMode
     * @return ein byte array mit dem komprimierten Inhalt der String-Collection
     */
    public static byte[] pack(final Collection<String> strings) {
        String res = join(strings, "\n");
        return CompEx.Compress(res, CompEx.ZIP);
    }

    /**
     * compress an array of single-lined Strings into a byte array
     * 
     * @param strings
     *            an array of String that must not contain newline (\n) characters
     * @return a byte array with the ZIP-compressed contents of the String array
     */
    public static byte[] pack(final String[] strings) {
        String res = join(strings, "\n");
        return CompEx.Compress(res, CompEx.ZIP);
    }

    /**
     * Unpack a Zip-compressed byte-Array in a List of Strings.
     * 
     * @param pack
     *            a packed byte array as created by pack()
     * @return an ArrayList of Strings
     * @see pack(String[])
     * @see pack(Collection<String>)
     */
    public static List<String> unpack(final byte[] pack) {
        try {
            String raw = new String(CompEx.expand(pack), default_charset);
            return splitAL(raw, "\n");
        } catch (Exception ex) {
            return null; // Sollte sowieso nie vorkommen
        }

    }

    /**
     * Eine Hashtable in ein komprimiertes Byte-Array umwandeln
     * 
     * @param hash
     *            die Hashtable
     * @param compressMode
     *            GLZ, HUFF, BZIP2
     * @param ExtInfo
     *            Je nach Kompressmode ntige zusatzinfo
     * @return das byte-Array mit der komprimierten Hashtable
     * @deprecated compressmode is always ZIP now.
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public static byte[] flatten(final Hashtable hash, final int compressMode, final Object ExtInfo) {
        ByteArrayOutputStream baos = null;
        OutputStream os = null;
        ObjectOutputStream oos = null;
        try {
            baos = new ByteArrayOutputStream(hash.size() * 30);
            switch (compressMode) {
            case GUESS:
            case ZIP:
                os = new ZipOutputStream(baos);
                ((ZipOutputStream) os).putNextEntry(new ZipEntry("hash"));
                break;
            case BZIP:
                os = new CBZip2OutputStream(baos);
                break;
            case HUFF:
                os = new HuffmanOutputStream(baos, (HuffmanTree) ExtInfo, 0);
                break;
            case GLZ:
                os = new GLZOutputStream(baos, hash.size() * 30);
                break;
            default:
                os = baos;
            }

            oos = new ObjectOutputStream(os);
            oos.writeObject(hash);
            if (os != null) {
                os.close();
            }
            baos.close();
            return baos.toByteArray();
        } catch (Exception ex) {
            ExHandler.handle(ex);
            return null;
        }
    }

    /**
     * Ein mit flatten() erzeugtes Byte-Array wieder in eine HAshtable zurckverwandeln
     * 
     * @param flat
     *            Die komprimierte Hashtable
     * @param compressMode
     *            Expnad-Modus
     * @param ExtInfo
     * @return die Hastbale
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public static Hashtable fold(final byte[] flat, final int compressMode, final Object ExtInfo) {
        ObjectInputStream ois = null;
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(flat);
            switch (compressMode) {
            case BZIP:
                ois = new ObjectInputStream(new CBZip2InputStream(bais));
                break;
            case HUFF:
                ois = new ObjectInputStream(new HuffmanInputStream(bais));
                break;
            case GLZ:
                ois = new ObjectInputStream(new GLZInputStream(bais));
                break;
            case ZIP:
                ZipInputStream zis = new ZipInputStream(bais);
                zis.getNextEntry();
                ois = new ObjectInputStream(zis);
                break;
            case GUESS:
                Hashtable<Object, Object> res = fold(flat, ZIP, null);
                if (res == null) {
                    res = fold(flat, GLZ, null);
                    if (res == null) {
                        res = fold(flat, BZIP, null);
                        if (res == null) {
                            res = fold(flat, HUFF, ExtInfo);
                            if (res == null) {
                                return null;
                            }
                        }
                    }
                }
                return res;
            default:
                ois = new ObjectInputStream(bais);
                break;
            }

            Hashtable<Object, Object> res = (Hashtable<Object, Object>) ois.readObject();
            ois.close();
            bais.close();
            return res;
        } catch (Exception ex) {
            // ExHandler.handle(ex); deliberately don't mind
            return null;
        }

    }

    static private String ObjectToString(final Object o) {
        if (o instanceof String) {
            return "A" + (String) o;
        }
        if (o instanceof Integer) {
            return "B" + ((Integer) o).toString();
        }
        if (o instanceof Serializable) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos;
            try {
                oos = new ObjectOutputStream(baos);
                oos.writeObject(o);
                oos.close();
                byte[] ret = baos.toByteArray();
                return "Z" + enPrintable(ret);

            } catch (IOException e) {
                ExHandler.handle(e);
                return null;
            }
        }
        return null;
    }

    static private Object StringToObject(final String s) {
        String sx = s.substring(1);
        char pref = s.charAt(0);
        switch (pref) {
        case 'A':
            return sx;
        case 'B':
            return (new Integer(Integer.parseInt(sx)));
        case 'Z':
            byte[] b = dePrintable(sx);
            try {
                ByteArrayInputStream bais = new ByteArrayInputStream(b);
                ObjectInputStream ois = new ObjectInputStream(bais);
                Object ret = ois.readObject();
                ois.close();
                bais.close();
                return ret;
            } catch (Exception ex) {
                ExHandler.handle(ex);
                return null;
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static Hashtable foldStrings(final String s) {
        Hashtable h = new Hashtable();
        if (StringTool.isNothing(s)) {
            return h;
        }
        String[] elems = s.split(flattenSeparator);
        if (!elems[0].equals("FS1")) {
            return null;
        }
        for (int i = 1; i < elems.length; i++) {
            String[] elem = elems[i].split("=", 2);
            if (elem.length != 2) { // log.log("Fehler in
                // Hash-Reprsentation",Log.ERRORS);
                return null;
            }
            Object k = StringToObject(elem[0].trim());
            Object v = StringToObject(elem[1].trim());
            if ((k == null) || (v == null)) {
                return null;
            }
            h.put(k, v);
        }
        return h;
    }

    /** gibt true zurck, wenn das Objekt kein String oder null oder "" ist */
    static public boolean isNothing(final Object n) {
        if (n == null) {
            return true;
        }
        if (n instanceof String) { // if(((String)n).equals("")) return true;
            if (((String) n).trim().equals("")) {
                return true;
            }
            return false;
        }
        return true;
    }

    /**
     * Gibt true zurck, wenn das Feld null ist, leer ist, oder nur Leerstrings enthlt
     */
    static public boolean isEmpty(final String[] f) {
        if (f == null) {
            return true;
        }
        for (int i = 0; i < f.length; i++) {
            if (!isNothing(f[i])) {
                return false;
            }
        }
        return true;
    }

    /** Verleicht zwei byte-Arrays */
    static public boolean compare(final byte[] a, final byte[] b) {
        if (a.length == b.length) {
            for (int i = 0; i < a.length; i++) {
                if (a[i] != b[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Sucht einen String in einem String-Array und gibt dessen Index zurck. Die Suche erfolgt ohne
     * Bercksichtigung von Gross/Kleinschreibung.
     * 
     * @return den index von val in arr oder -1 wenn nicht gefunden.
     */
    static public int getIndex(final String[] arr, final String val) {
        for (int i = 0; i < arr.length; i++) {
            if (val.equalsIgnoreCase(arr[i])) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Verlngert oder krzt einen String.
     * 
     * @param where
     *            LEFT vorne fllen, RIGHT hinten fllen
     * @param chr
     *            Zeichen zum Fllen
     * @param src
     *            Quellstring
     * @param size
     *            erwnschte Lnge
     * @return der neue String
     */
    static public String pad(final int where, final char chr, final String src, final int size) {
        int diff = size - src.length();
        if (diff > 0) {
            StringBuffer s = new StringBuffer(diff);
            for (int i = 0; i < diff; i++) {
                s.append(chr);
            }
            if (where == LEFT) {
                return s + src;
            }
            return src + s;
        }
        return src.substring(0, size);
    }

    /**
     * Erstellt einen String aus mehreren nacheinander folgenden Strings
     * 
     * @param str
     *            der zu multiplizierende string
     * @param num
     *            Zahl der Multiplikationen
     */
    static public String filler(final String str, int num) {
        StringBuilder s = new StringBuilder(num);
        while (num-- > 0) {
            s.append(str);
        }
        return s.toString();
    }

    public static String RectangleToString(int x, int y, int w, int h) {
        StringBuilder sb = new StringBuilder();
        sb.append(x).append(",").append(y).append(",").append(w).append(",").append(h);
        return sb.toString();
    }

    /**
     * Verknpft die Elemente eines String-Arrays mittels tren zu einem String
     * 
     * @param arr
     *            - String Array
     * @param tren
     *            - Verbindingsstring
     * @return den verknpften String
     */
    static public String join(final String[] arr, final String tren) {
        if ((arr == null) || (arr.length == 0)) {
            return "";
        }
        StringBuffer res = new StringBuffer(100);
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == null) {
                continue;
            }
            res.append(arr[i]).append(tren);
        }
        String r2 = res.toString();
        return r2.replaceFirst(tren + "$", "");
    }

    public static String join(final Iterable<String> i, final String tren) {
        StringBuilder ret = new StringBuilder();
        Iterator<String> it = i.iterator();
        while (it.hasNext()) {
            ret.append(it.next());
            if (it.hasNext()) {
                ret.append(tren);
            }
        }
        return ret.toString();
    }

    /**
     * Wandelt ein Byte-Array in einen druckbaren String um. (Alle Bytes werden in ihre Nibbles
     * zerlegt, diese werden hnlich wie mit base64 als Zeichen gespeichert
     */
    public static String enPrintable(final byte[] src) {
        return enPrintable(src, 70); // compatibility
    }

    static public String enPrintable(final byte[] src, final int offset) {
        byte[] out = new byte[src.length * 2];
        for (int i = 0; i < src.length; i++) {
            out[2 * i] = (byte) ((src[i] >> 4) + offset);
            out[2 * i + 1] = (byte) ((src[i] & 0x0f) + offset);
        }
        try {
            return new String(out, default_charset);
        } catch (Exception ex) {
            ExHandler.handle(ex);
            return null;
        }
    }

    /**
     * Wandelt einen mit enPrintable erhaltenen String in ein byte-Array zurck.
     */
    public static byte[] dePrintable(final String src) {
        return dePrintable(src, 70); // compatibility
    }

    static public byte[] dePrintable(final String src, final int offset) {
        byte[] input = null;
        try {
            input = src.getBytes(default_charset);
        } catch (Exception ex) {
            ExHandler.handle(ex);
            return null;
        }
        byte[] out = new byte[input.length / 2];
        for (int i = 0; i < out.length; i++) {
            out[i] = (byte) ((input[2 * i] - offset) * 16 + (input[2 * i + 1] - offset));
        }
        return out;
    }

    /**
     * Convert a byte array into a String that consists strictly only of numbers and capital
     * Letters. This can be useful for transmission over 7-Bit-Channels (In fact, 4 bit channels
     * would suffice) or Web Forms that would need URLConversion otherwise.
     * 
     * @param src
     *            the source array
     * @return a String that is 2 times the length of src + 3 Bytes and consists only of [0-9A-P]*
     */
    public static String enPrintableStrict(byte[] src) {
        if (src == null) {
            return null;
        }
        byte[] out = new byte[(src.length << 1) + 3];
        try {
            out[0] = 'E'; // header
            out[1] = 'P';
            out[2] = '1'; // Version
            for (int i = 0; i < src.length; i++) {
                int i1 = (src[i] & 0xff) >> 4;
                byte o1 = (byte) (i1 + 65);
                byte o2 = (byte) ((src[i] & 0x0f) + 65);
                out[2 * i + 3] = o1;
                out[2 * i + 4] = o2;
            }
            return new String(out, default_charset);

        } catch (Exception ex) {
            ExHandler.handle(ex);
            return null;
        }
    }

    /**
     * Convert a String that was created with enPrintableStrict() back into a byte array
     * 
     * @param src
     *            a String previously created by enPrintableStrict
     * @return a byte array with the original data or null on errors
     */
    public static byte[] dePrintableStrict(String src) {
        byte[] input = null;
        try {
            input = src.getBytes(default_charset);
            if ((input[0] != 'E') || (input[1] != 'P')) {
                return null;
            }
        } catch (Exception ex) {
            ExHandler.handle(ex);
            return null;
        }
        byte[] out = new byte[(input.length - 3) >> 1];
        for (int i = 0; i < out.length; i++) {
            int o1 = input[2 * i + 3] - 65;
            int o2 = input[2 * i + 4] - 65;
            out[i] = (byte) ((o1 << 4) + o2);
        }
        return out;
    }

    /**
     * Gibt eine zufllige und eindeutige Zeichenfolge zurck
     * 
     * @param salt
     *            Ein beliebiger String oder null
     */
    public static String unique(final String salt) {
        if (ipHash == 0) {
            Iterator<String> it = NetTool.IPs.iterator();
            while (it.hasNext()) {
                ipHash += (it.next()).hashCode();
            }
        }

        long t = System.currentTimeMillis();
        int t1 = System.getProperty("user.name").hashCode();
        long t2 = ((long) ipHash) << 32;
        long t3 = Math.round(Math.random() * Long.MAX_VALUE);
        long t4 = t + t1 + t2 + t3;
        if (salt != null) {
            long t0 = salt.hashCode();
            t4 ^= t0;
        }
        t4 += sequence++;
        if (sequence > 99999) {
            sequence = 0;
        }
        long idx = sequence % salties.length;
        char start = salties[(int) idx];
        return new StringBuilder().append(start).append(Long.toHexString(t4))
                .append(Long.toHexString((long) Math.random() * 1000)).append(sequence).toString();
    }

    /**
     * make sure a String is never null
     * 
     * @param in
     *            a String or null
     * @return "" if in was null, in otherwise
     */
    public static String unNull(final String in) {
        return (in == null) ? "" : in;
    }

    /**
     * Dem StreamTokenizer nachempfundene Klasse, die auf einem String arbeitet. Kann gequotete und
     * geklammerte ausdrcke als token zusammenfassen. Wirft exceptions bei unmatched quotes oder
     * klammern.
     * 
     * @author Gerry Weirich
     * 
     */
    static public class tokenizer {
        /** Betrachte in " eingeschlossene Phrasen als ein token */
        public static final int DOUBLE_QUOTED_TOKENS = 1;
        /** Betrachte in ' eingeschlossene Phrasen als ein token */
        public static final int SINGLE_QUOTED_TOKENS = 2;
        /**
         * In () geklammerte phrasen als ein token betrachten. Verschachtelte Klammern werden
         * unverndert bernommen
         */
        public static final int ROUND_BRACKET_TOKENS = 4;
        /** In [] geklammerte Phrasen als ein token betrachten */
        public static final int EDGE_BRACKET_TOKENS = 8;
        /** in {} geklammerte Phrasen als ein token betrachten */
        public static final int CURLY_BRACKET_TOKENS = 16;
        /** Zeilenende bricht token ab */
        public static final int CRLF_MATTERS = 32;
        private final String delim;
        private final int mode;
        private int pos;
        private final String mine;

        /**
         * Einziger Konstruktor
         * 
         * @param m
         *            der Quellstring
         * @param delim
         *            Zeichen, die als Tokengrenze betrachtet werden
         * @param mode
         *            OR-Kombination der obigen Token-Konstanten
         */
        public tokenizer(final String m, final String delim, final int mode) {
            mine = m;
            this.delim = delim;
            this.mode = mode;
            pos = 0;
        }

        /** Splittet den String auf und liefert die tokens als List */
        @SuppressWarnings("unchecked")
        public List<String> tokenize() throws IOException {
            ArrayList ret = new ArrayList();
            StringBuffer token = new StringBuffer();
            while (pos < mine.length()) {
                char c = mine.charAt(pos++);
                if (delim.indexOf(c) != -1) {
                    ret.add(token.toString());
                    token.setLength(0);
                    continue;
                }
                token.append(c);
                switch (c) {
                case '\"':
                    if ((mode & DOUBLE_QUOTED_TOKENS) != 0) {
                        token.append(readToMatching('\"', '\"'));
                    }
                    break;
                case '\'':
                    if ((mode & SINGLE_QUOTED_TOKENS) != 0) {
                        token.append(readToMatching('\'', '\''));
                    }
                    break;
                case '(':
                    if ((mode & ROUND_BRACKET_TOKENS) != 0) {
                        token.append(readToMatching('(', ')'));
                    }
                    break;
                case ')':
                    if ((mode & ROUND_BRACKET_TOKENS) != 0) {
                        throw new IOException("unmatched bracket");
                    }
                    break;
                case '[':
                    if ((mode & EDGE_BRACKET_TOKENS) != 0) {
                        token.append(readToMatching('[', ']'));
                    }
                    break;
                case ']':
                    if ((mode & EDGE_BRACKET_TOKENS) != 0) {
                        throw new IOException("unmatched bracket");
                    }
                    break;
                case '{':
                    if ((mode & CURLY_BRACKET_TOKENS) != 0) {
                        token.append(readToMatching('{', '}'));
                    }
                    break;
                case '}':
                    if ((mode & CURLY_BRACKET_TOKENS) != 0) {
                        throw new IOException("unmatched bracket");
                    }
                }
            }
            ret.add(token.toString());
            return ret;
        }

        private StringBuffer readToMatching(final char open, final char close) throws IOException {
            StringBuffer ret = new StringBuffer();
            int level = 1;
            while (pos < mine.length()) {
                char c = mine.charAt(pos++);
                ret.append(c);
                if (c == close) {
                    if (--level == 0) {
                        return ret;
                    }
                } else if (c == open) {
                    level++;
                } else if (c == '\r') {
                    if ((mode & CRLF_MATTERS) != 0) {
                        throw new IOException("Unexpected end of line while looking for " + close);
                    }
                }
            }
            throw new IOException("Unexpected end of line while looking for " + close);
        }
    }

    public interface flattenFilter {
        boolean accept(Object key);
    }

    /**
     * Versucht herauszufinden, ob ein Name weiblich ist
     * 
     * @param name
     *            der Name
     * @return true wenn der Name vielleicht weiblich ist
     */
    public static boolean isFemale(final String name) {
        if (isNothing(name)) {
            return false;
        }
        final String[] suffices = { "a", "is", "e", "id", "ah", "eh", "th" };
        for (String s : suffices) {
            if (name.endsWith(s)) {
                return true;
            }
        }
        return false;
    }

    public static boolean isMailAddress(final String in) {
        if (StringTool.isNothing(in)) {
            return false;
        }
        // up to 7 characters in device for james@bond.invalid
        return in.matches("\\w[\\w|\\.\\-]+@\\w[\\w\\.\\-]+\\.[a-zA-Z]{2,7}");
        // oder \w[\w|\.\-]+@\w[\w\.\-]+\.[a-zA-Z]{2,4}
    }

    /**
     * Test whether a String is an IPV4 or IPV6-Address
     * 
     * @param in
     *            a String that is possibly an ipv4 or ipv6-Address
     * @return true if ir seems to be an IP-Address
     */
    public static boolean isIPAddress(final String in) {
        if (in.matches(ipv4address)) {
            return true;
        }
        if (in.matches(ipv6address)) {
            return true;
        }
        return false;
    }

    /**
     * Return the first word of the given String
     */
    public static String getFirstWord(final String in) {
        if (isNothing(in)) {
            return "";
        }
        String[] words = in.split(wordSeparators);
        return words[0];
    }

    /**
     * Return the first line if the given String but at most maxChars
     */
    public static String getFirstLine(final String in, final int maxChars) {
        if (isNothing(in)) {
            return "";
        }
        String[] lines = in.split(lineSeparators);
        if (lines[0].length() > maxChars) {
            int ix = lines[0].lastIndexOf(' ', maxChars);
            return lines[0].substring(0, ix);
        }
        return lines[0];
    }

    /**
     * Gibt das Wort des Inhalts zurck das an oder unmittelbar vor der angegebenen Position ist
     * 
     * @return Das mit an dieser Stelle befindliche Wort des Strings, <code>String.empty</code>
     *         falls kein Wort dort ist idt oder der Index ausserhalb des Textbereichs ist
     */
    public static String getWordAtIndex(String text, int index) {
        char c;
        if (index < 0 || text == null || index > text.length()) {
            return "";
        }
        int start;
        int end;
        for (start = index - 1; start >= 0; start--) {
            c = text.charAt(start);
            if (wordSeparatorChars.indexOf(c) != -1) {
                start++;
                break;
            }
        }
        if (start < 0) {
            start = 0;
        }
        for (end = index; end < text.length(); end++) {
            c = text.charAt(end);
            if (wordSeparatorChars.indexOf(c) != -1) {
                break;
            }
        }
        if (end > text.length()) {
            end = text.length() + 1;
        }
        return text.substring(start, end);
    }

    @SuppressWarnings("unchecked")
    public static void dumpHashtable(final Log log, final Hashtable table) {
        Set<String> keys = table.keySet();
        log.log("Dump Hashtable\n", Log.INFOS);
        for (String key : keys) {
            log.log(key + ": " + table.get(key).toString(), Log.INFOS);
        }
        log.log("End dump\n", Log.INFOS);
    }

    /**
     * Change first lettere to uppercase, other letters to lowercase
     * 
     * @param orig
     *            the word to change (at least 2 characters)
     * @return the normalized word. Tis will return orig if orig is less than 2 characters
     */
    public static String normalizeCase(final String orig) {
        if (orig == null) {
            return "";
        }
        if (orig.length() < 2) {
            return orig;
        }
        return orig.substring(0, 1).toUpperCase() + orig.substring(1).toLowerCase();
    }

    /**
     * Convert first Character to uppercase. leave rest unchanged
     * 
     * @param orig
     *            the original String
     * @return the original String with first Character uppercase
     */
    public static String capitalize(final String orig) {
        if (orig == null) {
            return "";
        }
        if (orig.length() < 2) {
            return orig;
        }
        return orig.substring(0, 1).toUpperCase() + orig.substring(1);
    }

    /**
     * Zwei Strings verleichen. Bercksichtigen, dass einer oder beide auch Null sein knnten.
     * 
     * @param a
     *            erster String
     * @param b
     *            zweiter String
     * @return -1,0 oder 1
     */
    public static int compareWithNull(String a, String b) {
        if (a == null) {
            if (b == null) {
                return 0;
            } else {
                return -1;
            }
        } else if (b == null) {
            return 1;
        } else {
            return a.compareTo(b);
        }
    }

    /**
     * String wenn ntig krzen
     * 
     * @param orig
     *            Originalstring
     * @param len
     *            maximal zulssige Lenge
     * @return den String, der maximal len Zeichen lang ist
     */
    public static String limitLength(final String orig, final int len) {
        if (orig == null) {
            return "";
        }
        if (orig.length() > len) {
            return orig.substring(0, len);
        }
        return orig;
    }

    /**
     * String aus einem Array holen. Leerstring, wenn der angeforderte Index ausserhalb des Arrays
     * liegt
     * 
     * @param array
     * @param index
     * @return
     */
    public static String getSafe(final String[] array, final int index) {
        if ((index > -1) && (array.length > index)) {
            return array[index];
        }
        return "";
    }

    /**
     * Parse a String but don't throw expetion if not parsable. Return 0 instead
     * 
     * @param string
     * @return
     */
    public static int parseSafeInt(String string) {
        if (string == null) {
            return 0;
        }
        try {
            return Integer.parseInt(string.trim());
        } catch (NumberFormatException ne) {
            return 0;
        }
    }

    /**
     * Parse a Double from a string but don't throw an Exception if not parseable. Return 0.0
     * instead.
     * 
     * @param string
     *            a String containing probably a Double
     * @return always a double. 0.0 if the origin was 0.0 or null or not a Double
     */
    public static double parseSafeDouble(String string) {
        if (string == null) {
            return 0.0;
        }
        try {
            return Double.parseDouble(string.trim());
        } catch (NumberFormatException ne) {
            return 0.0;
        }
    }

    /**
     * String mit unterschiedlicher mglicher Schreibweise in einheitliche Schreibweise bringen
     * 
     * @param in
     *            ein String
     * @return derselbe String, aber alle mglicherweise kritische Zeichen durch _ ersetzt.
     */
    public static String unambiguify(final String in) {
        String ret = in.toLowerCase();
        ret = ret.replaceAll("([^a-z]|ue|oe|ae)", "_");
        ret = ret.replaceAll("__+", "_");
        return ret;
    }

    /**
     * convert a String from a source encoding to this platform's default encoding
     * 
     * @param src
     *            the source string
     * @param srcEncoding
     *            the name of the encoding of the source
     * @return the transcoded String or the source String if the encoding is not supported
     */
    public static String convertEncoding(String src, String srcEncoding) {
        try {
            byte[] bytes = src.getBytes();
            return new String(bytes, srcEncoding);
        } catch (UnsupportedEncodingException e) {
            return src;
        }
    }

    /**
     * convert a String Array from a source encoding to this platform's default encoding
     * 
     * @param src
     *            the source Array
     * @param srcEncoding
     *            the name of the encoding of the source
     * @return the transcoded Array or the source Array if the encoding is not supported
     */
    public static String[] convertEncoding(String[] src, String srcEncoding) {
        String[] ret = new String[src.length];
        for (int i = 0; i < src.length; i++) {
            ret[i] = convertEncoding(src[i], srcEncoding);
        }
        return ret;
    }

    /**
     * Eine beliebige Ziffernfolge mit der Modulo-10 Prfsumme verpacken
     * 
     * @param number
     *            darf nur aus Ziffern bestehen
     * @return die Eingabefolge, ergnzt um ihre Prfziffer
     */
    public static String addModulo10(final String number) {
        int row = 0;
        String nr = number.replaceAll("[^0-9]", "");
        for (int i = 0; i < nr.length(); i++) {
            int col = Integer.parseInt(nr.substring(i, i + 1));
            row = mod10Checksum[row][col];
        }
        return number + Integer.toString(mod10Checksum[row][10]);

    }

    /**
     * Die Modulo-10-Prfsumme wieder entfernen
     * 
     * @param number
     *            eine um eine prfziffer ergnzte Zahl
     * @return die Zahl ohne prfziffer oder null, wenn die Prfziffer falsch war.
     */
    public static String checkModulo10(final String number) {
        String check = number.substring(0, number.length() - 1);
        String should = addModulo10(check);
        if (should.equals(number)) {
            return check;
        }
        return null;
    }

    /** Array fr den modulo-10-Prfsummencode */
    private static final int[][] mod10Checksum = { { 0, 9, 4, 6, 8, 2, 7, 1, 3, 5, 0 },
            { 9, 4, 6, 8, 2, 7, 1, 3, 5, 0, 9 }, { 4, 6, 8, 2, 7, 1, 3, 5, 0, 9, 8 },
            { 6, 8, 2, 7, 1, 3, 5, 0, 9, 4, 7 }, { 8, 2, 7, 1, 3, 5, 0, 9, 4, 6, 6 },
            { 2, 7, 1, 3, 5, 0, 9, 4, 6, 8, 5 }, { 7, 1, 3, 5, 0, 9, 4, 6, 8, 2, 4 },
            { 1, 3, 5, 0, 9, 4, 6, 8, 2, 7, 3 }, { 3, 5, 0, 9, 4, 6, 8, 2, 7, 1, 2 },
            { 5, 0, 9, 4, 6, 8, 2, 7, 1, 3, 1 } };

    private static final char[] salties = { 'q', 'w', 'e', 'r', 't', 'z', 'u', 'o', 'i', 'p', 'a', 's', 'd', 'f',
            'g', 'h', 'j', 'k', 'l', 'y', 'x', 'c', 'v', 'b', 'n', 'm', 'Q', 'A', 'Y', 'W', 'E', 'D', 'C', 'R', 'F',
            'V', 'T', 'G', 'B', 'Z', 'H', 'N', 'U', 'J', 'M', 'I', 'K', 'O', 'L', 'P' };
}