Package de.innovationgate.utils

Source Code of de.innovationgate.utils.WGUtils

/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.innovationgate.utils;

import java.awt.Component;
import java.awt.Container;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.CharacterIterator;
import java.text.DecimalFormat;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipOutputStream;

import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.text.html.parser.ParserDelegator;

import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.iterators.EnumerationIterator;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.vfs.Capability;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileType;
import org.apache.log4j.Logger;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;


/**
* Container object for diverse static utility methods.
*/
public abstract class WGUtils {
   
    /**
     * Name of a directory link file, used by {@link #createDirLink(File, String)} and {@link #resolveDirLink(File)}
     */
    public static final String DIRLINK_FILE = "dirlink.xml";

    private static final String ASN1_ESCAPE_CHARS = ",+\"\\<>;";
    private static final String ASN1_FIRSTCHAR_ESCAPE_CHARS = " #";
   
   
    private WGUtils() {
    }
   
  
    private static Method _threadLocalRemoveMethod = null;
    static {
        try {
            _threadLocalRemoveMethod = ThreadLocal.class.getMethod("remove", new Class[] {});
        }
        catch (Exception e) {
        }
    }

    static class PlainTextParserCallback extends javax.swing.text.html.HTMLEditorKit.ParserCallback {

        private boolean _ignoreWhitespace = false;

        private String _divider = "";

        private StringBuffer _text = new StringBuffer();

        public PlainTextParserCallback(boolean ignoreWhitespace, String divider) {
            _ignoreWhitespace = ignoreWhitespace;
            _divider = divider;
        }

        public void handleText(char[] arg0, int arg1) {

            String newText = new String(arg0);
            if (this._ignoreWhitespace == true && newText.trim().equals("")) {
                return;
            }

            if (this._text.length() > 0) {
                this._text.append(this._divider);
            }
            this._text.append(newText);
        }

        public String getText() {
            return _text.toString();
        }

        public void resetText() {
            _text = new StringBuffer();
        }
    }

    static class PropertyComparator implements Comparator {

        private String _prop;

        public PropertyComparator(String prop) {
            _prop = prop;
        }

        /*
         * (Kein Javadoc)
         *
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(Object arg0, Object arg1) {

            JXPathContext con0 = JXPathContext.newContext(arg0);
            JXPathContext con1 = JXPathContext.newContext(arg1);

            Comparable val0 = (Comparable) con0.getValue(_prop);
            Comparable val1 = (Comparable) con1.getValue(_prop);
            return val0.compareTo(val1);

        }
    }

    /**
     * A full german date/time format: dd.MM.yyyy HH:mm:ss SSS
     */
    public static final java.text.DateFormat DATEFORMAT_FULL = new java.text.SimpleDateFormat("dd.MM.yyyy HH:mm:ss SSS");

    /**
     * A normal german date format: dd.MM.yyyy
     */
    public static final java.text.DateFormat DATEFORMAT_STANDARD = new java.text.SimpleDateFormat("dd.MM.yyyy");
   
    public static final java.text.DecimalFormat DECIMALFORMAT_STANDARD = new java.text.DecimalFormat("#,##0");

    /**
     * A normal german time format: HH:mm:ss
     */
    public static final java.text.DateFormat TIMEFORMAT_STANDARD = new java.text.SimpleDateFormat("HH:mm:ss");

    /**
     * Deletes the given file or the contents of a folder.
     * If the file is a directory this method will
     * recurse through the contents of the directory and delete any file and
     * directory within, then finally will delete the initial directory (if param "deleteFileItself" is true).
     * This can be used to delete directory trees of any size and depth (yes, this is a warning).
     *
     * @param file
     *            The file or folder to delete/clear
     * @param deleteFileItself
     *            Setting this to false will only delete the files in the given folder (if it is one), but not the folder itself. If it is no folder this is a noop.
     */
    public static void delTree(File file, boolean deleteFileItself) {

        if (file.isDirectory()) {
            File[] subFiles = file.listFiles();
            for (int i = 0; i < subFiles.length; i++) {
                WGUtils.delTree(subFiles[i], true);
            }
        }
        if (deleteFileItself) {
            boolean fileDeleted = false;
            int tryCounter = 1;
            while (!fileDeleted) {
                fileDeleted = file.delete();
                if (!fileDeleted) {
                    tryCounter++;
                    if (tryCounter > 5) {
                        throw new IllegalStateException("Undeletable file " + file.getAbsolutePath());
                    }
                   
                    try {
                        Thread.sleep(100);
                    }
                    catch (InterruptedException e) {
                    }
                }
            }
        }
    }
   
    /**
     * Deletes the given file. If the file is a directory this method will
     * recurse through the contents of the directory and delete any file and
     * directory within, then finally will delete the initial directory. This
     * can be used to delete directory trees of any size and depth.
     *
     * @param file
     *            The file to delete.
     */
    public static void delTree(File file) {
        delTree(file, true);
    }

    /**
     * Determines if the given object is any kind of collection
     *
     * @param obj
     *            The object to test
     * @return true, if it is a collection.
     */
    public static boolean isCollection(Object obj) {

        return (obj != null && java.util.Collection.class.isAssignableFrom(obj.getClass()));
    }

    /**
     * Serializes a collection to a single String using the given divider.
     * Collections serialized by this method can be deserialized again by
     * {@link de.innovationgate.utils#WGUtils.deserializeCollection deserializeCollection}.
     * This version takes a special object formatter object, that formats the
     * collection elements before they are added to the result string. The
     * formatter can be used to convert non-string values in the collection to
     * strings by a special method.
     *
     * @param col
     *            The collection
     * @param divider
     *            The divider
     * @param formatter
     *            The formatter
     * @param includeNulls
     *            Specify null to treat null values in the collection as emtpy strings, false to omit them in output
     * @return A string containing the collection items connected by the given
     *         divider
     */
    public static String serializeCollection(java.util.Collection col, String divider, ObjectFormatter formatter, boolean includeNulls) {

        if (divider == null) {
            divider = "";
        }

        if (formatter == null) {
            formatter = DefaultObjectFormatter.getInstance();
        }

        java.util.Iterator elements = col.iterator();
        StringBuffer output = new StringBuffer();
        Object element;
        boolean firstElement = true;
       
        while (elements.hasNext()) {
            element = elements.next();
            if (element == null) {
                if (includeNulls) {
                    element = "";
                }
                else {
                    continue;
                }
            }

            if (!firstElement) {
                output.append(divider);
            }
            else {
                firstElement = false;
            }
           
            try {
                output.append(formatter.format(element));
            }
            catch (FormattingException e) {
                Logger.getLogger("wga.utils").error("Error formatting element", e);
                output.append(String.valueOf(element));
            }
        }
        return output.toString();

    }
   
    /**
     * Serializes a collection to a single String using the given divider.
     * Collections serialized by this method can be deserialized again by
     * {@link de.innovationgate.utils#WGUtils.deserializeCollection deserializeCollection}.
     * This version takes a special object formatter object, that formats the
     * collection elements before they are added to the result string. The
     * formatter can be used to convert non-string values in the collection to
     * strings by a special method.
     *
     * This method version omits null values in the collection.
     *
     * @param col
     *            The collection
     * @param divider
     *            The divider
     * @param formatter
     *            The formatter
     * @return A string containing the collection items connected by the given
     *         divider
     */
    public static String serializeCollection(java.util.Collection col, String divider, ObjectFormatter formatter) {
        return serializeCollection(col, divider, formatter, false);
    }

    /**
     * Creates a List based on string, that contains substrings divided by a
     * special divider string. Can be used to recreate lists that were
     * serialized by
     * {@link #serializeCollection serializeCollection}.
     *
     * This command: WGUtils.deserializeCollection("a;b;c", ";")
     *
     * Would create a list containing the three strings "a", "b" and "c".
     *
     *
     * @param colString
     *            The string that contains the list information
     * @param divider
     *            divider string that separates the substrings.
     * @param trimTokens
     *            Decides if the substrings are trimmed (via String.trim())
     *            before they are put into the list
     * @param stringDelimiter
     *            Describes a character that should be treated as string
     *            delimiter, like " or '. Text contained in these signs will be
     *            ignored when the split operation is done. Use null if you do
     *            not want to ignore strings.
     * @return A list consisting of the substrings inside the parameter string.
     *         Divider strings are omitted.
     */
    public static List<String> deserializeCollection(String colString, String divider, boolean trimTokens, Character stringDelimiter) {

        List<String> col = new ArrayList<String>();
        int dividerLength = divider.length();
        if (dividerLength == 0) {
            throw new IllegalArgumentException("Cannot deserialize collection with empty string as divider");
        }

        String searchString = colString;
        if (stringDelimiter != null) {
            searchString = clearStrings(colString, stringDelimiter.charValue(), (divider.trim().equals("") ? 'X' : ' '));
        }

        int startPos = 0;
        int nowPos = searchString.indexOf(divider);
        String token;
        while (nowPos != -1) {
            token = colString.substring(startPos, nowPos);
            if (trimTokens) {
                token = token.trim();
            }
            col.add(token);
            startPos = nowPos + dividerLength;
            nowPos = searchString.indexOf(divider, startPos);
        }

        if (startPos <= colString.length()) {
            token = colString.substring(startPos);
            if (trimTokens) {
                token = token.trim();
            }
            col.add(token);
        }

        return col;
    }

    /**
     * Clears out "internal strings" from a text that contains some kind of
     * program code. I.e. if the text itself contains string delimiter
     * characters, like " or ', the contents between these characters is
     * regarded a string. Its contents will be cleared in the text version that
     * is returned by this method. This is useful to prepare a text for an
     * operation, that may not react on the contents of strings inside it. This
     * method regards the character \ as an escape sign for string delimiters.
     * So delimiter characters that are prefixed by a \ will be ignored.
     *
     * @param colString
     *            The text
     * @param stringDelimiter
     *            The character that introduces and closes strings inside the
     *            text
     * @param replaceChar
     *            The character that is used to clear out strings.
     * @return The text with cleared out internal strings
     */
    public static String clearStrings(String colString, char stringDelimiter, char replaceChar) {

        CharacterIterator it = new StringCharacterIterator(colString);
        StringBuffer out = new StringBuffer();
        boolean inAString = false;
        char prevChar = ' ';
        for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
            // Look for string introducor
            if (c == stringDelimiter && prevChar != '\\') {
                inAString = !inAString;
                out.append(stringDelimiter);
            }
            else if (inAString) {
                out.append(replaceChar);
            }
            else {
                out.append(c);
            }
            prevChar = c;
        }
        return out.toString();

    }

    /**
     * Creates a List based on string, that contains substrings divided by a
     * special divider string. Can be used to recreate lists that were
     * serialized by
     * {@link #serializeCollection serializeCollection}.
     *
     * This command: WGUtils.deserializeCollection("a;b;c", ";")
     *
     * Would create a list containing the three strings "a", "b" and "c".
     *
     *
     * @param colString
     *            The string that contains the list information
     * @param divider
     *            divider string that separates the substrings.
     * @param trimTokens
     *            Decides if the substrings are trimmed (via String.trim())
     *            before they are put into the list
     * @return A list consisting of the substrings inside the parameter string.
     *         Divider strings are omitted.
     */
    public static List<String> deserializeCollection(String colString, String divider, boolean trimTokens) {
        return deserializeCollection(colString, divider, trimTokens, null);
    }

    /**
     * Creates a List based on string, that contains substrings divided by a
     * special divider string. Can be used to recreate lists that were
     * serialized by
     * {@link #serializeCollection serializeCollection}.
     *
     * This command: WGUtils.deserializeCollection("a;b;c", ";")
     *
     * Would create a list containing the three strings "a", "b" and "c".
     *
     *
     * @param colString
     *            The string that contains the list information
     * @param divider
     *            divider string that separates the substrings.
     * @return A list consisting of the substrings inside the parameter string.
     *         Divider strings are omitted.
     */
    public static List<String> deserializeCollection(String colString, String divider) {
        return deserializeCollection(colString, divider, false);
    }

    /**
     * Serializes a collection (containing strings) to a single String using the
     * given divider. Collections serialized by this method can be deserialized
     * again by
     * {@link de.innovationgate.utils#WGUtils.deserializeCollection deserializeCollection}.
     *
     * @param col
     *            The collection
     * @param divider
     *            The divider
     * @return A string containing the collection items connected by the given
     *         divider
     */
    public static String serializeCollection(java.util.Collection col, String divider) {
        return WGUtils.serializeCollection(col, divider, null);
    }

    /**
     * Encodes an input string of plain text to it's XML representation. The
     * special characters &, <, > and " and all characters with character code >=
     * 127 are converted to numeric XML entities.
     *
     * @param input
     *            The plain text string to convert
     * @return The XML representation of the plain text.
     */
    public static String encodeXML(String input) {

        StringReader in = new StringReader(input);
        StringBuffer out = new StringBuffer();
        int ch;

        try {
            while ((ch = in.read()) != -1) {
                if (ch < 127 && ch != '&' && ch != '<' && ch != '>' && ch != '"') {
                    out.append((char) ch);
                }
                else {
                    out.append('&').append('#').append(ch).append(';');
                }
            }
        }
        catch (IOException exc) {
            exc.printStackTrace();
        }

        return out.toString();
    }

    /**
     * Joins the remaining tokens of a StringTokenizer, using the given divider
     *
     * @param tokenizer
     *            The StringTokenizer
     * @param delim
     *            The divider
     * @return A string containing the remaining tokens of the tokenizer
     *         connected by the given divider
     */
    public static String joinRemainingTokens(java.util.StringTokenizer tokenizer, String delim) {

        StringBuffer joined = new StringBuffer();
        while (tokenizer.hasMoreTokens()) {
            joined.append(tokenizer.nextToken());
            if (tokenizer.hasMoreTokens()) {
                joined.append(delim);
            }
        }
        return joined.toString();

    }

    /**
     * Old version of strReplace. No longer used.
     *
     * @deprecated
     * @param strText
     * @param strFrom
     * @param strTo
     * @param bMultiple
     * @return Converted string
     */
    public static String strReplaceOld(String strText, String strFrom, String strTo, boolean bMultiple) {

        int iFromLength = strFrom.length();
        int iToLength = strTo.length();

        int iOccurs = 0;
        String strOutput = new String(strText);

        iOccurs = strOutput.toLowerCase().indexOf(strFrom.toLowerCase(), 0);

        int iStartWith = 0;

        while (iOccurs != -1) {
            strOutput = strOutput.substring(0, iOccurs) + strTo + strOutput.substring(iOccurs + iFromLength);
            iStartWith = (iOccurs - 1) + iToLength + 1;

            if (bMultiple)
                iOccurs = strOutput.toLowerCase().indexOf(strFrom.toLowerCase(), iStartWith);
            else
                iOccurs = -1;
        }

        return strOutput;
    }

    /**
     * Replaces occurences of a substring inside a string by another substring.
     * This variant of the method takes a replace processor object that can be
     * used to further specify the replacing mechanism.
     *
     * @param strText
     *            The text to search for occurences of the substring
     * @param strFrom
     *            The substring to search for
     * @param proc
     *            The processor that will make the replacement and tell the
     *            method where to continue searching.
     * @param bMultiple
     *            Specify true if multiple occurences should be replaced.
     *            Specify false if only the first occurence should be replaced.
     * @param exactCase
     *            Determines if strings should be compared with exact case.
     *            If false, string are matched case insensitive
     * @return The string with occurences of substring replaced.
     */
    public static String strReplace(String strText, String strFrom, ReplaceProcessor proc, boolean bMultiple, boolean exactCase) {

        if (strText == null || strFrom == null) {
            return "";
        }

        if (proc == null) {
            proc = new DefaultReplaceProcessor("");
        }

        int iFromLength = strFrom.length();

        String strLCText = (exactCase ? strText : strText.toLowerCase());
        String strLCFrom = (exactCase ? strFrom : strFrom.toLowerCase());

        int iOccurs = strLCText.indexOf(strLCFrom, 0);
        int iStartWith = 0;
        StringWriter out = new StringWriter();

        try {
            while (iOccurs != -1) {
                out.write(strText.substring(iStartWith, iOccurs));
                int iTo = iOccurs + iFromLength;
                iStartWith = proc.replace(strText, iOccurs, iTo, out);

                if (bMultiple)
                    iOccurs = strLCText.toLowerCase().indexOf(strLCFrom, iStartWith);
                else
                    iOccurs = -1;
            }

            if (iStartWith < strLCText.length()) {
                out.write(strText.substring(iStartWith));
            }

            return out.toString();
        }
        catch (IOException e) {
            e.printStackTrace();
            return strText;
        }
    }

    /**
     * Replaces occurences of a substring inside a string by another substring.
     * This variant of the method takes a replace processor object that can be
     * used to further specify the replacing mechanism. It matches strings case sensitive.
     *
     * @param text
     *            The text to search for occurences of the substring
     * @param substring
     *            The substring to search for
     * @param proc
     *            The processor that will make the replacement and tell the
     *            method where to continue searching.
     * @param multiple
     *            Specify true if multiple occurences should be replaced.
     *            Specify false if only the first occurence should be replaced.
     * @return The string with occurences of substring replaced.
     */
    public static String strReplace(String text, String substring, ReplaceProcessor proc, boolean multiple) {
        return strReplace(text, substring, proc, multiple, false);
    }

    /**
     * Replaces occurences of a substring inside a string by another substring.
     *
     * @param strText
     *            The text to search for occurences of the substring
     * @param strFrom
     *            The substring to search for
     * @param strTo
     *            The substring used to replace the string in strFrom
     * @param bMultiple
     *            Specify true if multiple occurences should be replaced.
     *            Specify false if only the first occurence should be replaced.
     * @param exactCase
     *            Determines if strings should be compared with exact case.
     *            If false, string are matched case insensitive
     * @return The string with occurences of substring replaced.
     */
    public static String strReplace(String strText, String strFrom, String strTo, boolean bMultiple, boolean exactCase) {
        return strReplace(strText, strFrom, new DefaultReplaceProcessor(strTo), bMultiple, exactCase);
    }

    /**
     * Replaces occurences of a substring inside a string by another substring.
     * This variant of the method matches strings case sensitive.
     *
     * @param strText
     *            The text to search for occurences of the substring
     * @param strFrom
     *            The substring to search for
     * @param strTo
     *            The substring used to replace the string in strFrom
     * @param bMultiple
     *            Specify true if multiple occurences should be replaced.
     *            Specify false if only the first occurence should be replaced.
     * @return The string with occurences of substring replaced.
     */
    public static String strReplace(String strText, String strFrom, String strTo, boolean bMultiple) {
        return strReplace(strText, strFrom, strTo, bMultiple, false);
    }

    /**
     * Converts a base64-encoded string into bytes, then constructs a string
     * from these bytes and returns it
     *
     * @param base64
     *            base64-encoded information as string
     * @return The decoded string
     */
    public static String base64toString(String base64) {

        try {
            byte[] data = Base64.decode(base64);
            InputStreamReader streamReader = new InputStreamReader(new ByteArrayInputStream(data));
            int ch = -1;
            StringBuffer result = new StringBuffer();
            for (ch = streamReader.read(); ch != -1; ch = streamReader.read()) {
                result.append((char) ch);
            }
            return result.toString();
        }
        catch (Exception exc) {
            exc.printStackTrace();
            return null;
        }

    }

    /**
     * Determines the location of the classfile that defines the given class.
     * This can be useful if a class is in classpath multiple times to determine
     * which version is really used.
     *
     * @param className
     *            The class to find.
     * @param refClass
     *            The class that itself will use this class in its code. It's
     *            classloader will be used to find the location
     * @return The location of the classfile as path.
     */
    public static String which(String className, Class refClass) {

        if (!className.startsWith("/")) {
            className = "/" + className;
        }
        className = className.replace('.', '/');
        className = className + ".class";

        java.net.URL classUrl = refClass.getResource(className);

        if (classUrl != null) {
            return classUrl.getFile();
        }
        else {
            return null;
        }
    }
   
    /**
     * Determines the location of the classfile that defines the given class.
     * This can be useful if a class is in classpath multiple times to determine
     * which version is really used.
     *
     * @param className
     *            The class to find.
     * @param cl
     *            The classloader to use to load the class
     * @return The location of the classfile as path.
     */
    public static String which(String className, ClassLoader cl) {

        /*if (!className.startsWith("/")) {
            className = "/" + className;
        }*/
        className = className.replace('.', '/');
        className = className + ".class";

        java.net.URL classUrl = cl.getResource(className);

        if (classUrl != null) {
            return classUrl.getFile();
        }
        else {
            return null;
        }
    }

    /**
     * Counts the occurences of a special text phrase in another text
     *
     * @param text
     *            The text, in which will be searched
     * @param subtext
     *            The text phrase, that is searched in the text of the first
     *            parameter
     * @return The number of occurences
     */
    public static int countOccurences(String text, String subtext) {

        int count = 0;
        int idx = 0;
        int lenSubtext = subtext.length();
        while ((idx = text.indexOf(subtext, idx + lenSubtext)) != -1) {
            count++;
        }

        return count;

    }

    /**
     * Tests a collection retrieved by lotus.domino.Document.getItemValue() if
     * it is an empty notes field.
     *
     * @param col
     *            The collection
     * @return true if it is the content of an empty notes field
     */
    public static boolean isEmptyNotesField(Collection col) {

        if (col == null || col.size() == 0 || (col.size() == 1 && col.toArray()[0].equals(""))) {
            return true;
        }
        else {
            return false;
        }

    }

    /**
     * Tests the object for "emptiness" which is defined as one of the following conditions
     * <ul>
     * <li>The object is null
     * <il>The object is an empty string
     * <li>The object is a collection with size 0 or is only containing one object that itself is "empty" (applies to the given emptiness rules)
     * </ul>
     * @param obj
     * @return true if the object is empty
     */
    public static boolean isEmpty(Object obj) {

        if (obj == null) {
            return true;
        }

        if (obj instanceof String) {
            return obj.equals("");
        }

        if (obj instanceof Collection) {
            Collection col = (Collection) obj;
            if (col.size() == 0) {
                return true;
            }
            if (col.size() == 1) {
                Object firstValue = col.iterator().next();
                return isEmpty(firstValue);
            }
        }

        return false;

    }

    /**
     * Encodes an input string of plain text to it's HTML representation.
     * Therefor:
     * <ul>
     * <li>All line feeds are converted to <br/> (if param useHTMLTags is true)
     * <li>The special characters &, <, > and " are converted to entities
     * <li> All characters with character code >= 127 are converted to HTML entities (if param reduceToASCII==true)
     * </ul>
     *
     * @param input
     *            The plain text string to convert
     * @param reduceToASCII
     *            true if you want all non-ASCII characters to be converted to entities
     * @param useHTMLTags
     *            true if the conversion should put out HTML tags as representiations of special characters (like <br> for \n)
     * @return The HTML representation of the plain text.
     */
    public static String encodeHTML(String input, boolean reduceToASCII, boolean useHTMLTags) {

        if (input == null) {
            return "";
        }

        StringReader in = new StringReader(input);
        StringBuffer out = new StringBuffer();
        int ch;

        try {
            while ((ch = in.read()) != -1) {
                if (ch == '\n') {
                    if (useHTMLTags) {
                        out.append("<br>");
                    }
                }
                else if ((!reduceToASCII || ch < 127) && ch != '&' && ch != '<' && ch != '>' && ch != '"') {
                    out.append((char) ch);
                }
                else {
                    out.append('&').append('#').append(ch).append(';');
                }
            }
        }
        catch (IOException exc) {
            exc.printStackTrace();
        }

        return out.toString();
    }
   
    /**
     * Encodes an input string of plain text to it's HTML representation.
     * Therefor:
     * <ul>
     * <li>All line feeds are converted to <br/>
     * <li>The special characters &, <, > and " are converted to entities
     * <li> All characters with character code >= 127 are converted to HTML entities (if param reduceToASCII==true)
     * </ul>
     *
     * @param input
     *            The plain text string to convert
     * @param reduceToASCII
     *            true if you want all non-ASCII characters to be converted to entities
     * @return The HTML representation of the plain text.
     */
    public static String encodeHTML(String input, boolean reduceToASCII) {
        return encodeHTML(input, reduceToASCII, true);
    }
   
    /**
     * Encodes an input string of plain text to it's HTML representation.
     * Therefor:
     * <ul>
     * <li>All line feeds are converted to <br/>
     * <li>The special characters &, <, > and " are converted to entities
     * <li> All characters with character code >= 127 are converted to HTML entities
     * </ul>
     *
     * @param input
     *            The plain text string to convert
     * @return The HTML representation of the plain text.
     */
    public static String encodeHTML(String input) {
        return encodeHTML(input, true, true);
    }

    /**
     * Tests if any value in one collection is part of another collection
     *
     * @param col1
     *            First collection
     * @param col2
     *            Second collection
     * @return The first matching object, if any matches. If there are not
     *         matches returns null.<
     */
    public static Object containsAny(Collection col1, Collection col2) {

        Collection largerCol;
        Collection smallerCol;

        if (col1.size() > col2.size()) {
            largerCol = col1;
            smallerCol = col2;
        }
        else {
            largerCol = col2;
            smallerCol = col1;
        }

        Iterator smallerValues = smallerCol.iterator();
        Object value;
        while (smallerValues.hasNext()) {
            value = smallerValues.next();
            if (largerCol.contains(value)) {
                return value;
            }
        }

        return null;

    }

    /**
     * Sorts a collection by a property of the collection contents. The property
     * to use for sorting is specified as JXPath. JXPath makes JavaBean
     * properties accessible via XPath expressions. e.g. if the collection
     * contains beans with a method "getStatus", this method can sort the list
     * based on the return value of this method by specifying this XPath:
     * /status
     *
     * Cascaded access to bean properties is also possible. This XPath:
     * /address/zipcode
     *
     * Tests this method-cascade bean.getAddress().getZipcode()
     *
     * The sorting is always ascending. You can have descending sorting by
     * reversing the results.
     *
     * @param col
     *            The collection to sort
     * @param property
     *            The property to use for sorting, specified as JXPath.
     * @return The sorted collection as list
     */
    public static List sortByProperty(Collection col, String property) {
        PropertyComparator comparator = new PropertyComparator(property);
        List list = new ArrayList(col);
        Collections.sort(list, comparator);
        return list;

    }

    /**
     * Sets a property to a map only if the map does not yet contain the
     * property key.
     *
     * @param props
     *            The map
     * @param key
     *            The property key
     * @param value
     *            The value of the property
     */
    public static void setDefaultProperty(Map props, Object key, Object value) {
        if (!props.containsKey(key)) {
            props.put(key, value);
        }
    }

    /**
     * Hashes a given password using the SHA-1 algorithm. SHA-1 is a
     * one-way-encrypting. The password hash cannot be decoded. However similar
     * passwords will result in similar password hashes.
     *
     * @param pwd
     *            The password
     * @return The password hash
     * @throws NoSuchAlgorithmException
     */
    public static String hashPassword(String pwd) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        byte[] digestedPwdBytes = messageDigest.digest(pwd.getBytes());
        return Base64.encode(digestedPwdBytes);
    }

    /**
     * Converts a byte array to a MD5 hexadecimal string
     * @param input The byte input
     * @return The MD5 string
     * @throws NoSuchAlgorithmException
     */
    public static String createMD5HEX(byte[] input) throws NoSuchAlgorithmException {
        MessageDigest algorithm = MessageDigest.getInstance("MD5");
        algorithm.reset();
        algorithm.update(input);
        byte messageDigest[] = algorithm.digest();

        StringBuffer hexString = new StringBuffer();
        for (int i = 0; i < messageDigest.length; i++) {
            hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
        }
        return hexString.toString();
    }

    /**
     * Converts an inputstream to a MD5 hex string
     * the inputstream is implicit closed after reading
     * @param input
     * @return MD5 checksum
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static String createMD5HEX(InputStream input) throws NoSuchAlgorithmException, IOException {
     
      MessageDigest algorithm = MessageDigest.getInstance("MD5");
      algorithm.reset();
     
      InputStream in = null;
      try {
        in = new BufferedInputStream(input);
      byte[] buffer = new byte[1024];
      int len = in.read(buffer);           
      while (len > 0) {
        algorithm.update(buffer, 0, len);
        len = in.read(buffer);
      }
     
      byte messageDigest[] = algorithm.digest();   
      StringBuffer hexString = new StringBuffer();
          for (int i = 0; i < messageDigest.length; i++) {
              hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
          }
          return hexString.toString();
      } finally {
        if (in != null) {
          in.close();
        }
      }
    }
   
    /**
     * Retrieves the value of java.lang.Runtime.maxMemory() which is only
     * available if using a JRE of version 1.4 or higher. This value shows the
     * maximum size of the java heap in bytes (as set by the vm parameter -Xmx).
     * If this method is not available because of an older java runtime, the
     * method returns -1.
     *
     * @return The max size of the heap or -1 if this information is not
     *         available.
     */
    public static long getMaxHeap() {

        long maxHeap = -1;

        try {
            Method maxHeapMethod = Runtime.class.getMethod("maxMemory", new Class[] {});
            if (maxHeapMethod != null) {
                Number maxHeapNumber = (Number) maxHeapMethod.invoke(Runtime.getRuntime(), null);
                maxHeap = maxHeapNumber.longValue();
            }
        }
        catch (Exception e) {
        }
        return maxHeap;

    }

    /**
     * Converts a HTML to plain text by removing all HTML tags.
     *
     * @param html
     *            The html
     * @param divider
     *            The divider by which separate text fragments that were parsed
     *            from the HTML should be divided.
     * @param ignoreWhitespace
     *            Specify true if pure whitespace text fragments should be
     *            ignored
     * @return The plain text
     * @throws IOException
     */
    public static String toPlainText(String html, String divider, boolean ignoreWhitespace) throws IOException {

        PlainTextParserCallback callback = new PlainTextParserCallback(ignoreWhitespace, divider);
        ParserDelegator parserDelegator = new javax.swing.text.html.parser.ParserDelegator();
        parserDelegator.parse(new java.io.StringReader(html), callback, true);
        return callback.getText();

    }

    /**
     * Copies a file from source to target.
     * This method also is able to copy complete directories. The following behaviour applies by condition of the parameter file types:<br>
     * <p>
     * <b>source is regular file. Target is regular file or nonexistent:</b><br>
     * Source file is copied to target file. Target is overwritten if it already exists.
     * </p>
     * <p>
     * <b>source is regular file. Target is directory:</b><br>
     * Source file is copied into target directory. Gets the same name as the source file.
     * </p>
     * <p>
     * <b>source is directory.. Target is directory or nonexistent:</b><br>
     * Complete source directory is copied into the target directory. The copied directory becomes a sub directory of the target which is created if it does not yet exist.
     * </p>
     * @param source Source file or directory
     * @param target Target file or directory
     * @throws IOException
     */
    public static void copyFile(File source, File target) throws IOException {

        if (source.isDirectory()) {
            File subtarget = new File(target, source.getName());
            copyDirContent(source, subtarget);
        }
        else {
            if (!target.exists()) {
                if (!target.createNewFile()) {
                    throw new IOException("Unable to create target file");
                }
            }
            else if (target.isDirectory()) {
                target = new File(target, source.getName());
            }
   
            byte[] buf = new byte[2048];
            InputStream in = new BufferedInputStream(new FileInputStream(source));
            OutputStream out = new BufferedOutputStream(new FileOutputStream(target));
            int len;
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
   
            out.flush();
            in.close();
            out.close();
        }

    }

    /**
     * Copies the contents of a directory into a target directory.
     * @param source The source directory
     * @param targetDir The target directory
     * @throws IOException
     */
    public static void copyDirContent(File source, File targetDir) throws IOException {
       
        if (!source.exists() || !source.isDirectory()) {
            throw new IOException("Source file is no directory: " + source.getPath());
        }
       
        if (!targetDir.exists()) {
            if (!targetDir.mkdirs()) {
                throw new IOException("Unable to create target directory " + targetDir.getPath());
            }
        }
        else if (!targetDir.isDirectory()) {
            throw new IOException("Cannot copy to directory " + targetDir.getPath() + " because there already is a regular file of that name");
        }
       
        File[] files = source.listFiles();
        for (int i = 0; i < files.length; i++) {
            copyFile(files[i], targetDir);
        }
    }
   
    /**
     * Variant of {@link #copyFile(File, File)} that takes file paths as argument
     * @param source File path of source file
     * @param target File path of target file
     * @throws IOException
     */
    public static void copyFile(String source, String target) throws IOException {
        copyFile(new File(source), new File(target));
    }

    /**
     * Returns english counting endings for numbers "st", "nd", "rd" and "th" like in "1st", "2nd", "3rd", "4th", "21st" etc.
     * @param no The number to determine ending for
     * @return The ending string exclusive the number
     */
    public static String countEnding(int no) {

        int remainder = no % 10;
        if (remainder == 1) {
            return "st";
        }
        else if (remainder == 2) {
            return "nd";
        }
        else if (remainder == 3) {
            return "rd";
        }
        else {
            return "th";
        }

    }

    /**
     * Returns a formatted number inclusive counting ending (see {@link #countEnding(int)}.
     * @param no The number to format
     * @return The formatted number
     */
    public static String countFormat(int no) {
        return new DecimalFormat().format(no) + countEnding(no);
    }

    /**
     * Variant of {@link #countFormat(int)} that takes a string that will be parsed to an integer
     * @param noStr A string that is parsable as integer
     * @return  The formatted number
     */
    public static String countFormat(String noStr) {
        return countFormat(Integer.parseInt(noStr));
    }

    /**
     * Writes all data from a reader to a writer
     * @param read The reader
     * @param write The writer
     * @param bufferSize The size of the buffer to use for data transfer
     * @throws IOException
     */
    public static void inToOut(Reader read, Writer write, int bufferSize) throws IOException {

        char[] buf = new char[bufferSize];
        int len;
        while ((len = read.read(buf)) != -1) {
            write.write(buf, 0, len);
        }

    }


    /**
     * Writes all data from an input stream to an output stream
     * @param read The input stream
     * @param write The output stream
     * @param bufferSize The size of the buffer to use for data transfer
     * @throws IOException
     */
    public static void inToOut(InputStream read, OutputStream write, int bufferSize) throws IOException {

        byte[] buf = new byte[bufferSize];
        int len;
        while ((len = read.read(buf)) != -1) {
            write.write(buf, 0, len);
        }

    }
   
    /**
     * Writes a chosen amount of data from an input stream to an output stream
     * @param read The input stream
     * @param write The output stream
     * @param length number of bytes to write
     * @param bufferSize The size of the buffer to use for data transfer
     * @throws IOException
     */
    public static void inToOutLimited(InputStream read, OutputStream write, int length, int bufferSize) throws IOException {

        byte[] buf = new byte[bufferSize];
       
        int len;
        while (length >= bufferSize && (len = read.read(buf)) != -1) {
            write.write(buf, 0, len);
            length -= len;
        }

        int aByte;
        while (length > 0 && (aByte = read.read()) != -1) {
            write.write(aByte);
            length--;
        }


    }

    /**
     * Searches a map for entries whose string keys start with the given prefix.
     * Extracts these entries and puts them in the result map, cutting off the
     * prefix from the keys.
     *
     * @param map
     *            The map to search
     * @param prefix
     *            The prefix. Entries with a string key that starts with this
     *            prefix will get extracted.
     * @return The map with the extracted entries. entry keys are the keys of
     *         the original map minus the prefix.
     */
    public static Map extractMapByPrefix(Map map, String prefix) {

        int prefixLen = prefix.length();
        Map result = new HashMap();
        Iterator keys = map.keySet().iterator();
        Object key;
        String keyStr;
        while (keys.hasNext()) {
            key = keys.next();
            if (key instanceof String) {
                keyStr = (String) key;
                if (keyStr.startsWith(prefix)) {
                    result.put(keyStr.substring(prefixLen), map.get(key));
                }
            }
        }
        return result;

    }

    /**
     * Returns a file for a folder. If the folder does not exist it is created.
     * @param parent The parent folder of the retrieved folder
     * @param name The name of the folder to retrieve
     * @return The folder
     * @throws IOException
     */
    public static File getOrCreateFolder(File parent, String name) throws IOException {
        if (!parent.isDirectory()) {
            throw new IllegalArgumentException("Parent file is no folder: " + parent.getPath());
        }

        File folder = new File(parent, name);
        if (!folder.exists()) {
            if (!folder.mkdir()) {
                throw new IOException("Unable to create directory '" + folder.getPath() + "'");
            }
        }
        if (!folder.isDirectory()) {
            throw new IllegalArgumentException("There is already a file of this name: " + name);
        }
        return folder;
    }

    /**
     * Returns a VFS file object for a folder. If the  folder does not exist it is created.
     * @param parent The parent folder of the retrieved folder
     * @param name The name of the folder to retrieve
     * @return The folder
     * @throws IOException
     */
    public static FileObject getOrCreateFolder(FileObject parent, String name) throws IOException {
        if (!parent.getType().equals(FileType.FOLDER)) {
            throw new IllegalArgumentException("Parent file is no folder: " + parent.getName().getPathDecoded());
        }

        FileObject folder = parent.resolveFile(name);
        if (!folder.exists()) {
            if (!folder.getFileSystem().hasCapability(Capability.CREATE)) {
                throw new IOException("File system of file " + folder.getURL().toString() + " is read only");
            }
            folder.createFolder();
        }
        if (!folder.getType().equals(FileType.FOLDER)) {
            throw new IllegalArgumentException("There is already a file of this name: " + name);
        }
        return folder;
    }

    /**
     * Cutoff milliseconds from a time value
     * @param time The time value
     * @return A time value with removed milliseconds
     */
    public static long cutoffTimeMillis(long time) {

        return (long) Math.floor(((double) time) / 1000) * 1000;

    }

    /**
     * Cutoff milliseconds from a date
     * @param date The date
     * @return The date without milliseconds
     */
    public static Date cutoffDateMillis(Date date) {

        long time = date.getTime();
        time = cutoffTimeMillis(time);
        return new Date(time);

    }

    /**
     * Sets a swing/awt container and all of its child components enabled or
     * disabled
     *
     * @param con
     *            The container
     * @param enabled
     *            The state to set. true for enabled. false for disabled
     */
    public static void setAllEnabled(Container con, boolean enabled) {

        Component[] children = con.getComponents();
        for (int i = 0; i < children.length; i++) {
            children[i].setEnabled(enabled);
        }
        con.setEnabled(enabled);

    }

    /**
     * Updates a list with the state represented by another list, with as less
     * changes as possible. Elements that are new in newCol will get added to
     * list. Elements that do not exist in newCol but exist in list will get
     * removed from list. Elements that both, list and newCol, contain will
     * remain. This method can be used to update lists that are stored via
     * hibernate. Just replacing the old list with a new one would result in
     * hibernate removing all rows from the table and re-insert all values anew.
     * If lists are updated with this method, hibernate can focus on the real
     * removements and added elements without touching unmodified values.
     * WARNING: Only use this when the sorting order of the list is of no
     * importance!
     *
     * @param list
     * @param newCol
     */
    public static void updateList(List list, Collection newCol) {
        List newList = new ArrayList(newCol);
        list.retainAll(newList);
        newList.removeAll(list);
        list.addAll(newList);
    }

    /**
     * Creates a new list that contains the same elements than the parameter
     * list, but with all string elements converted to lower case
     *
     * @param listOriginal
     * @return The list with all strings converted to lower case
     */
    public static List toLowerCase(List listOriginal) {

        List list = new ArrayList();
        Object elem;
        for (int i = 0; i < listOriginal.size(); i++) {
            elem = listOriginal.get(i);
            if (elem instanceof String) {
                list.add(((String) elem).toLowerCase());
            }
            else {
                list.add(elem);
            }
        }

        return list;

    }
   
    /**
     * Creates a new list that contains the string representations
     * of all elements of the original list
     *
     * @param listOriginal
     * @return The list with all elements converted to their string representation
     */
    public static List toString(List listOriginal) {

        List list = new ArrayList();
        Object elem;
        for (int i = 0; i < listOriginal.size(); i++) {
            elem = listOriginal.get(i);
            list.add(String.valueOf(elem));
        }

        return list;

    }

    /**
     * Makes a bitsise compare of two bitsets, represented by ints. Returns true
     * if all of the bits in bitset 2 are contained in bitset 1. (It may seem
     * strange to make a special method for this, since the operation is not
     * that complicated, but it will make the code that uses this method more
     * readable than the bit operation).
     *
     * @param bitset1
     *            The bitset 1
     * @param bitset2
     *            The int whose bits should be tested
     * @return True if all of bits in bitset2 are contained in bitset1.
     */
    public static boolean testBits(int bitset1, int bitset2) {
        return (bitset1 & bitset2) == bitset2;
    }

    /**
     * Reduces a string to a given maximum length. If the string must be
     * truncated to match the max length the last two characters of the string
     * will get converted to "..".
     *
     * @param str
     *            The string to reduce
     * @param length
     *            The maximum length of the string
     * @return The, eventually truncated, string
     */
    public static String reduce(String str, int length) {
       
        if (length == 0) {
            return str;
        }

        if (str == null) {
            return null;
        }
        else if (str.length() > length) {
            return str.substring(0, length - 2) + "..";
        }
        else {
            return str;
        }

    }

    /**
     * Converts a string to a boolean value, accepting "true", "t", "1", "yes"
     * and "y" as true, accepting "false", "f", "0", "no", "n" as false, and
     * throwing an IllegalArgumentException when none of these strings match.
     * The method's test is case-insensitive.
     *
     * @param expr
     *            The boolean string
     * @return The boolean value
     */
    public static boolean stringToBoolean(String expr) {
       
        String cleanExpr = expr.toLowerCase().trim();

        if (cleanExpr.equals("true") || cleanExpr.equals("t") || cleanExpr.equals("1") || cleanExpr.equals("yes") || cleanExpr.equals("y")) {
            return true;
        }
        else if (cleanExpr.equals("false") || cleanExpr.equals("f") || cleanExpr.equals("0") || cleanExpr.equals("no") || cleanExpr.equals("n")) {
            return false;
        }
        else {
            throw new IllegalArgumentException("Expression could not be interpreted as boolean: " + expr);
        }
    }
   
    /**
     * checks if the given string can be interpreted as boolean
     * accepting "true", "t", "1", "yes" and "y", "false", "f", "0", "no", "n"
     * @param expr
     * @return the boolean interpretation of the string
     */
    public static boolean isBooleanValue(String expr) {
      String cleanExpr = expr.toLowerCase().trim();
      if (cleanExpr.equals("true") || cleanExpr.equals("t") || cleanExpr.equals("1") || cleanExpr.equals("yes") || cleanExpr.equals("y") || cleanExpr.equals("false") || cleanExpr.equals("f") || cleanExpr.equals("0") || cleanExpr.equals("no") || cleanExpr.equals("n")) {
        return true;
      } else {
        return false;
      }
    }

    /**
     * Retrieves a boolean value from a map value. Will convert string
     * representations of booleans automatically by using
     * WGUtils.stringToBoolean().
     *
     * @param options
     *            The options map.
     * @param name
     *            The name of the option
     * @param defaultValue
     *            The default value to use if the option is not set or it's
     *            boolean value is not determinable
     * @return The boolean value if any could get determined, the default value
     *         otherwise
     */
    public static boolean getBooleanMapValue(Map options, String name, boolean defaultValue) {

        Object obj = options.get(name);
        if (obj == null) {
            return defaultValue;
        }
        else if (!(obj instanceof Boolean)) {
            try {
                return WGUtils.stringToBoolean(obj.toString());
            }
            catch (IllegalArgumentException e) {
                return defaultValue;
            }
        }
        else {
            return ((Boolean) obj).booleanValue();
        }

    }
   
    /**
     * Very simple method returning a parameter value if it is non null, or else a default value
     * @param value The value, returned when != null
     * @param defaultValue The default value, returned when value == null
     */
    public static <X> X getValueOrDefault(X value, X defaultValue) {
        if (value != null) {
            return value;
        }
        else {
            return defaultValue;
        }
    }

    /**
     * Removes single quotes from a string
     * @param strName The string
     * @return String without single quotes
     */
    public static String escapeSQ(String strName) {
        return strReplace(strName, "'", "", true);
    }
   
    /**
     * Escapes Strings to be used in ASN.1 attributes (like LDAP attributes)
     * @param str The string to escape
     * @return The escaped string
     */
    public static String escapeASN1(String str) {
       
        StringBuffer out = new StringBuffer();
        char c;
        boolean escape;
        for (int i=0; i < str.length(); i++) {
            c = str.charAt(i);
           
            escape = false;
            if (i == 0 && ASN1_FIRSTCHAR_ESCAPE_CHARS.indexOf(c) != -1) {
                escape = true;
            }
           
            if (escape == false && (i == str.length() - 1) &&  c == ' ') {
                escape = true;
            }
           
            if (escape == false && ASN1_ESCAPE_CHARS.indexOf(c) != -1) {
                escape = true;
            }
           
            if (escape) {
                out.append("\\");
            }
            out.append(c);
           
        }
       
        return out.toString();
       
    }
   
    /**
     * Escapes a string to be put out as JavaScript string literal.
     * Contained string delimiters are escaped so they do not terminate the literal.
     * This method also puts out the surrounding string delimiters ".
     * @param value The string literal
     * @return The escaped literal
     * @deprecated Use {@link #encodeJS(String)} instead
     */
    public static String escapeJsString(String value) {
        return "\"" + WGUtils.strReplace(value, "\"", "\\\"", true) + "\";\n";
    }

    /**
     * Removes the suffix part (i.e. the part after the last ".") from a file name
     * @param name The file name
     * @return File name w/o suffix
     */
    public static String removeFileNameSuffix(String name) {
        return name.substring(0, name.lastIndexOf("."));
    }

    /**
     * Reads data from a reader into a string
     * @param reader The reader
     * @return The string with the read data
     * @throws IOException
     */
    public static String readString(Reader reader) throws IOException {

        StringWriter writer = new StringWriter();
        inToOut(reader, writer, 2048);
        return writer.toString();

    }
   
    /**
     * Readers data from an input stream into a string
     * @param in The input stream
     * @param encoding The text encoding of the stream
     * @return The string with the read data
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    public static String readString(InputStream in, String encoding) throws IOException, UnsupportedEncodingException {
       
        InputStreamReader reader = new InputStreamReader(in, encoding);
        return readString(reader);
       
    }

    /**
     * Sorts the child elements of an element based on their indiviual XPath
     * result. This utility method should be used instead of direct sorting of
     * the elements()-list of a parent element, since this will fail with
     * exception on most occasions.
     *
     * @param parent
     *            The parent element
     * @param xpath
     *            The xpath that is evaluated on each child element
     */
    public static void sortChildElements(Element parent, String xpath) {

        // Create a copy of the elements list and sort it
        List list = new ArrayList(parent.elements());
        DocumentHelper.sort(list, xpath);

        // Iterate over sorted list. Remove and add all in order
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Element element = (Element) it.next();
            parent.remove(element);
            parent.add(element);
        }

    }

    /**
     * Zips the contents of a directory to a ZipOutputStream.
     * This method uses the {@link DirZipper} class with default settings. Use the class directly for more control.
     * @param zipDir The directory to zip up
     * @param zos The ZipOutputStream where the data is written to
     * @throws IOException
     */
    public static void zipDirectory(File zipDir, ZipOutputStream zos) throws IOException {
        DirZipper zipper = new DirZipper();
        zipper.zipDirectory(zipDir, zos);
    }
   
    /**
     * Calculates a relative file path, relative to the given parent path.
     * Example:
     * <code>
     * relativeFilePath("D:\Daten\WGAConfig\WGA32Test\designsync\mysql", "D:\Daten\WGAConfig\WGA32Test")
     * </code>
     * returns "designsync\mysql"
     * <p>
     * This works only when
     * <ul>
     * <li> path itself starts with parentPath
     * <li>parentPath is a syntacticly valid directory path (which does not mean that the directory must exist)
     * </ul>
     * <p>
     * If these conditions are not met, the method either throws an IllegalArgumentException (if param failIfNoParent==true)
     * or just returns the path again completely (if param failIfNoParent==false)
     * </p>
     * @param path The path from which a relative path should be extracted
     * @param parentPath A parent path of path, to which the calculated relative path should be relative to
     * @param failIfNoParent Controls the failure behaviour. See method description.
     * @return The relative path
     */
    public static String relativeFilePath(String path, String parentPath, boolean failIfNoParent) {
       
        // Test if parent path is the beginning of path
        if (!path.startsWith(parentPath)) {
            if (failIfNoParent) {
                throw new IllegalArgumentException("Path '" + parentPath + "' is no parent path of path '" + path + "'");
            }
            else {
                return path;
            }
        }
       
        // Test if parent path does not end inside a filename
        if (!parentPath.endsWith(SystemUtils.FILE_SEPARATOR)) {
            if (path.length() > parentPath.length() && !path.substring(parentPath.length(), parentPath.length() + 1).equals(SystemUtils.FILE_SEPARATOR)) {
                throw new IllegalArgumentException("Path '" + parentPath + "' is no parent directory of path '" + path + "'");
            }
        }
       
        int cutoffLength = (parentPath.endsWith(SystemUtils.FILE_SEPARATOR) ? parentPath.length() : parentPath.length() + 1);
        return path.substring(cutoffLength);
       
    }
   
    /**
     * A variant of {@link #relativeFilePath(String, String, boolean)} which always throws a IllegalArgumentException if the
     * parent path is not valid.
     * @param path
     * @param parentPath
     * @return The absolute path
     */
    public static String relativeFilePath(String path, String parentPath) {
        return relativeFilePath(path, parentPath, true);
    }
   
    /**
     * Finds an object inside any collection based on its hashcode.
     * @param col The collection to search
     * @param hash HashCode of the searched object
     * @return The object if it was contained in the collection, null if not
     */
    public static Object findObjectByHash(Collection col, int hash) {
       
        Iterator it = col.iterator();
        Object obj;
        while (it.hasNext()) {
            obj = it.next();
            if (obj.hashCode() == hash) {
                return obj;
            }
        }
       
        return null;
       
    }

    /**
     * Retrieves an DOM Element with a given name. The element is created if it does not yet exist.
     * @param parent The parent element of the element to retrieve
     * @param name The name of the element
     * @return The element
     */
    public static Element getOrCreateElement(Element parent, String name) {
   
        Element element = parent.element(name);
        if (element == null) {
            element = parent.addElement(name);
        }
   
        return element;
    }

    /**
     * Retrieves a DOM attribute with a given name. The attribute is created if it does not yet exist
     * @param element The element containing the attribute
     * @param name The name of the attribute
     * @param defaultValue The default value of the attribute, used when it must be created
     * @return The attribute
     */
    public static Attribute getOrCreateAttribute(Element element, String name, String defaultValue) {
   
        Attribute att = element.attribute(name);
        if (att == null) {
            element.addAttribute(name, defaultValue);
            att = element.attribute(name);
        }
        return att;
   
    }
   
    /**
     * Sets the selected items on a {@link JList}, which is a tedious task to do by hand.
     * @param selection The items that should be selected in the list
     * @param list The list itself
     */
    public static void setJListSelection(List selection, JList list) {
       
        // Load JList model data in array list
        List modelItems = new ArrayList();
        ListModel listModel = list.getModel();
        for (int i=0; i < listModel.getSize(); i++) {
            modelItems.add(listModel.getElementAt(i));
        }
       
        // Determine indices of selection items
        List indices = new ArrayList();
        Iterator items = selection.iterator();
        while (items.hasNext()) {
            Object item = items.next();
            int index = modelItems.indexOf(item);
            if (index != -1) {
                indices.add(new Integer(index));
            }
        }
       
        // Convert Integer list to int array (man, this is awkward...)
        int[] indicesArr = new int[indices.size()];
        for (int i=0; i < indices.size(); i++) {
            indicesArr[i] = ((Integer) indices.get(i)).intValue();
        }
       
        // Set selection
        list.setSelectedIndices(indicesArr);
       
    }
   
    /**
     * Extracts the messages of an Throwable and it's causes.
     * The message includes the Throwable's class name and the message itself.
     * The result list begins with the message of the given throwable and continues with the message of it's cause, and the causes of that cause.
     * This is continued until a cause throwable itself has no cause.
     * @param th The throwable
     * @return List of messages
     */
    public static List extractMessages(Throwable th) {
       
        List msg = new ArrayList();
        while (th != null) {
            msg.add(th.getClass().getName() + " - " + th.getMessage());
            if (msg instanceof Exception) {
                th = ((Exception) msg).getCause();
            }
            else {
                th = null;
            }
        }
        return msg;
       
    }

    /**
     * Fills the millisecond part of a date value with 999 if it is 000.
     * This can be used to round up time values with missing millisecond precision (e.g. from database columns)
     * that may not be lower than the actual time they refer to, which might be problematic when comparing these
     * dates to other values.
     * @param lastModified
     * @return The date with millsecond part filled, if it was empty, or the unmodified date
     */
    public static Date roundMillisUp(Date lastModified) {
       
        long time = lastModified.getTime();
        long millis = time % 1000;
        if (millis == 0) {
            time += 999;
            return new Date(time);
        }
        else {
            return lastModified;
        }
       
    }
   
   
    /**
     * Does a null safe compare on two objects. The objects are considered equal if:
     * - Both are null
     * - Both are non-null and a normal equals()-compare returns true
     * @param obj1 Compared object 1
     * @param obj2 Compared object 2
     * @param ignoreCase If true and both objects are strings, will use equalsIgnoreCase()
     * @return true if objects are considered equal
     */
    public static boolean nullSafeEquals(Object obj1, Object obj2, boolean ignoreCase) {
       
        if (obj1 == null && obj2 == null) {
            return true;
        }
       
        if (obj1 != null && obj2 != null) {
            if (ignoreCase && obj1 instanceof String && obj2 instanceof String) {
                return ((String) obj1).equalsIgnoreCase((String) obj2);
            }
            else {
                return obj1.equals(obj2);
            }
        }
       
        return false;
    }
   
    /**
     * Does a null safe compare on two objects. The objects are considered equal if:
     * - Both are null
     * - Both are non-null and a normal equals()-compare returns true
     * This is a variant of {@link #nullSafeEquals(Object, Object, boolean)} which will not use case-insensitive compare.
     * @param obj1 Compared object 1
     * @param obj2 Compared object 2
     * @return true if the objects are considered equal
     */
    public static boolean nullSafeEquals(Object obj1, Object obj2) {
        return nullSafeEquals(obj1, obj2, false);
    }
   
      /**
     * Returns the root cause throwable for the given throwable
     * @param e The throwable
     * @return The root cause
     */
    public static Throwable getRootCause(Throwable e) {
        while (e.getCause() != null) {
            e = e.getCause();
        }
        return e;
    }
   
    /**
     * Creates a synchronized map instance.
     * Uses the most effective available synchronized map in the current java runtime.
     * Either ConcurrentHashMap for Java 5 or Collections.synchronizedMap(new HashMap()) for older Java runtimes
     * @return A synchronized map instance
     */
    public static Map createSynchronizedMap() {
       
        if (SystemUtils.isJavaVersionAtLeast((float) 1.5)) {
            try {
                Map map = (Map) Class.forName("java.util.concurrent.ConcurrentHashMap").newInstance();
                return map;
            }
            catch (Exception e) {
                Logger.getLogger("wga.utils").error("Unable to instantiate ConcurrentHashMap although java version is >= 1.5", e);
            }
        }
       
        return Collections.synchronizedMap(new HashMap());
       
    }
   
    /**
     * Method to clear a value inside a ThreadLocal.
     * This method will use the JDK5 method ThreadLocal.remove() when available to do this task.
     * This will allow the ThreadLocalMap-Entry to be garbage collected. Otherwise it sets the Entry to the value of null.
     * @param tl The ThreadLocal whose value to clear
     */
    public static void removeThreadLocalValue(ThreadLocal tl) {
       
        if (_threadLocalRemoveMethod != null) {
            try {
                _threadLocalRemoveMethod.invoke(tl, new Object[]{});
                return;
            }
            catch (Exception e) {
                Logger.getLogger("wga.utils").error("Error removing thread local value", e);
            }
        }
       
        tl.set(null);
    }
   
    /**
     * Removes daytime information from a date, leaving date information only
     * @param date The date to strip daytime information from
     * @return The date only date object
     */
    public static Date dateOnly(Date date) {

    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
        cal.set(Calendar.AM_PM, 0);
        cal.set(Calendar.HOUR, 0);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    return cal.getTime();

  }
   
    /**
     * Removes date information from a date, leaving daytime information only
     * @param date The date to strip date information from
     * @return The daytime only date object
     */
  public static Date timeOnly(Date date) {

    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.clear(Calendar.YEAR);
    cal.clear(Calendar.MONTH);
    cal.clear(Calendar.DATE);
    cal.clear(Calendar.DAY_OF_WEEK);
    cal.clear(Calendar.DAY_OF_MONTH);
    return cal.getTime();

  }
 
  /**
   * Formats a date by WGAs default date format dd.MM.yyyy
   */
  public static String stdDateFormat(Date date) {
      return DATEFORMAT_STANDARD.format(date);
  }
 
   /**
    * Formats a date by WGAs default time format HH:mm:SS
    */
    public static String stdTimeFormat(Date date) {
        return DATEFORMAT_STANDARD.format(date);
    }
   
    /**
     * Formats a number by WGAs default decimal format #,##0
     */
    public static String stdFormat(Number num) {
        return DECIMALFORMAT_STANDARD.format(num.doubleValue());
    }
   
    /**
     * Puts all entries from map source to map target whose values are non-null.
     * This can be used to fill ConcurrentHashMaps that do not take null values.
     * @param source Map providing the entries
     * @param target Map getting all entries
     * @return A set of keys that contained null values and therefor were not put to target
     */
    public static Set putAllNonNullValues(Map source, Map target) {
       
        Iterator entries = source.entrySet().iterator();
        Set nullKeys = new HashSet();
        while (entries.hasNext()) {
            Map.Entry entry = (Map.Entry) entries.next();
            if (entry.getValue() != null) {
                target.put(entry.getKey(), entry.getValue());
            }
            else {
                nullKeys.add(entry.getKey());
            }
        }
        return nullKeys;
       
    }
   
        /**
     * Extracts a list of entries from any iterator.
     * May be useful with some commons collections classes like LinkedMap, that maintain order but give
     * no direct access to some ordered list, only via iterators. If the given iterator is a map iterator
     * the method returns the values of the map entries NOT the keys (because for key lists there already
     * is a method in the map itself).
     * @param iterator The iterator whose elements are put to the list
     * @return The list with the iterator elements
     */
    public static List extractEntryList(Iterator iterator) {
       
        List list = new ArrayList();
        while (iterator.hasNext()) {
            if (iterator instanceof MapIterator) {
                iterator.next();
                list.add(((MapIterator) iterator).getValue());
            }
            else {
                list.add(iterator.next());
            }
           
        }
        return list;
       
    }
   
    /**
     * Version of {@link #extractEntryList(Iterator)} which takes an Enumeration instead
     */
    public static List extractEntryList(Enumeration en) {
        return extractEntryList(new EnumerationIterator(en));
    }
   
    /**
     * Parses an integer from a string.
     * Other than the JRE functions this method also copes with integers that are expressed like floats, like 10.0
     * @param str A string representing a number.
     * @return The integer. If the string represented a float the integer part of that is returned
     */
    public static int parseInt(String str) {
       
        double dValue = Double.parseDouble(str);
        return (int) Math.floor(dValue);
       
    }
   
  /**
   * Creates a directory link file pointing to a target path
   * @param parentDir The directory to contain the link file
   * @param target The target path that the directory link should point to
   * @throws IOException
   */
  public static void createDirLink(File parentDir, String target) throws IOException {
    File link = new File(parentDir, DIRLINK_FILE);
    Document doc = DocumentFactory.getInstance().createDocument();
    Element dirlink = doc.addElement("dirlink");
    Element path = dirlink.addElement("path");
    path.addAttribute("location", target);
    XMLWriter writer = new XMLWriter(OutputFormat.createPrettyPrint());
    writer.setOutputStream(new FileOutputStream(link));
    writer.write(doc);
    writer.close();
  }
 
  /**
   * Resolves an eventually present directory link file. Use this with folders that either may be used themselves or that contain a directory link pointing to the directory to use.
   * @param file The directory that might contain a directory link file.
   * @return Either the path that an available directory link file points to or the given directory itself again.
   */
  public static File resolveDirLink(File file) {
    if (file != null && file.exists()) {
      if (file.isDirectory()) {
        File link = new File(file, DIRLINK_FILE);
        if (link.exists()) {
          // dir link present resolve
         
          Document doc;
          try {
            FileInputStream fileInputStream = new FileInputStream(link);
            String linkLocation = readDirLinkLocation(fileInputStream);
            if (linkLocation != null) {
              if (linkLocation.startsWith("../")) {
                return new File(file, linkLocation);
              } else {
                return new File(linkLocation);
              }
            }
          } catch (Exception e) {
            Logger.getLogger("wga.utils").error("Unable to resolve dir link. '" + link.getAbsolutePath() + "'.", e);
          }         
        }
      }
    }
    // no dir link or file does not exist - just return
    return file;
  }

    private static String readDirLinkLocation(InputStream fileInputStream) throws DocumentException, IOException {
        Document doc;
        SAXReader reader = new SAXReader();
        doc = reader.read(fileInputStream);
        Element dirlink = doc.getRootElement();
        String linkLocation = dirlink.element("path").attributeValue("location", null);
        fileInputStream.close();
        return linkLocation;
    }
 
    /**
     * Resolves an eventually present directory link file (variant with Commons VFS file objects). Use this with folders that either may be used themselves or that contain a directory link pointing to the directory to use.
     * @param file The directory that might contain a directory link file.
     * @return Either the path that an available directory link file points to or the given directory itself again.
     */
    public static FileObject resolveDirLink(FileObject file) throws FileSystemException {
     
      if (file != null && file.exists()) {
            if (file.getType().equals(FileType.FOLDER)) {
                FileObject link = file.resolveFile(DIRLINK_FILE);
                if (link.exists()) {
                    // dir link present resolve
                   
                    Document doc;
                    try {
                        InputStream fileInputStream = link.getContent().getInputStream();
                        String linkLocation = readDirLinkLocation(fileInputStream);
                        if (linkLocation != null) {
                            if (linkLocation.startsWith("../")) {
                                return file.resolveFile(linkLocation);
                            } else {
                                return file.getFileSystem().resolveFile(linkLocation);
                            }
                        }
                    } catch (Exception e) {
                        Logger.getLogger("wga.utils").error("Unable to resolve dir link. '" + link.getName().getPath() + "'.", e);
                    }                  
                }
            }
        }
        // no dir link or file does not exist - just return
        return file;
     
  }
 
  /**
   * Return the path of the given classes package, that must be used when loading resources from it
   * This returns the name of the package of the given class, converted to a resource path. You can use
   * the returned path to load non-class resources from the package folder.
   * @param clazz The class whose package is used
   */
  public static String getPackagePath(Class<? extends Object> clazz) {
      return WGUtils.strReplace(clazz.getPackage().getName(), ".", "/", true);
  }
   
    /**
     * Lowercases the string elements of a list. Elements of other types remain untouched.
     */
    public static void lowerCaseList(List list) {
       
        for (int idx=0; idx < list.size(); idx++) {
            Object element = list.get(idx);
            if (element instanceof String) {
                String str = (String) element;
                str = str.toLowerCase();
                list.set(idx, str);
            }
        }
       
    }
   
    /**
     * Reduces a user agent string to a given size and keeping the basic syntax intact when possible
     * The method tries to reduce the content of the outer bracket content so the user agent is still parseable
     * @param str User agent string
     * @param len Maximum length allowed
     * @return Truncated user agent string
     */
    public static String reduceUserAgentString(String str, int len) {
       
        if (str == null) {
            return null;
        }
       
     // Enforce maximum size of len chars
        int oversize = str.length() - len;
       
        // If there is oversize we try to truncate the client string without breaking its format (B0000596A)
        if (oversize > 0) {
            int outerBracketStart = str.indexOf("(");
            int outerBracketEnd = str.lastIndexOf(")");
            if (outerBracketStart != -1 && outerBracketEnd != -1 && (outerBracketEnd - outerBracketStart - 2) >= oversize) {
                String bracketContent = str.substring(outerBracketStart + 1, outerBracketEnd);
                bracketContent = WGUtils.reduce(bracketContent, bracketContent.length() - oversize);
                str = str.substring(0, outerBracketStart + 1) + bracketContent + str.substring(outerBracketEnd);
            }
           
            // If we do not find these brackets, or their content is not long enough we must truncate the string without respecting its structure
            else {
                str = WGUtils.reduce(str, len);
            }
        }
       
        return str;
    }
   
    /**
     * executes the given runnable with timeout
     * @param runnable The runnable to execute
     * @param timeout The timeout in ms.
     * @throws WGTimeoutException when timeout occurs.
     * @throws InterruptedException when the runnable before timeout occurs.
     * @throws Throwable on errors of runnable execution.
     */
    public static void executeWithTimeout(RunnableWithExceptions runnable, long timeout) throws Throwable {
      TimeoutThread thread = new TimeoutThread(runnable);
      thread.start();
     
      thread.join(timeout);
 
      if (!thread.isFinished()) {
        thread.interrupt();       
        throw new WGTimeoutException("Execution of '" + runnable.getClass().getName() + "' timed out after " + timeout + " ms.");
      } else if (thread.getThrowable() != null) {
        throw thread.getThrowable();       
      }
    }
   
    private static class TimeoutThread extends Thread {
     
      private boolean _finished = false;


    private RunnableWithExceptions _runnable;

    private Throwable _throwable = null;
     
      public Throwable getThrowable() {
      return _throwable;
    }

    public TimeoutThread(RunnableWithExceptions runnable) {
        _runnable = runnable;
      }
     
    public void run() {
      if (_runnable != null) {
        try {
          _runnable.run();
        } catch (Throwable e) {
          _throwable  = e;
        }
      }
      _finished = true;
    }
     
    public boolean isFinished() {
      return _finished;
    }
    }
   
    /**
     * Thrown when {@link WGUtils#executeWithTimeout(RunnableWithExceptions, long)} runs on the timeout
     */
    public static class WGTimeoutException extends Exception {

    private static final long serialVersionUID = 1L;

    public WGTimeoutException() {
      super();
    }

    public WGTimeoutException(String message, Throwable cause) {
      super(message, cause);
    }

    public WGTimeoutException(String message) {
      super(message);
    }

    public WGTimeoutException(Throwable cause) {
      super(cause);
    }
     
    }
   
    /**
     * Interface for a runnable to use with {@link WGUtils#executeWithTimeout(RunnableWithExceptions, long)}
     */
    public static interface RunnableWithExceptions {
      public abstract void run() throws Throwable;
    }
   
    /**
     * Tool function to log category headers to a logger
     * @param log The logger
     * @param msg The title of the category header
     * @param level The level of category info, resulting in different category characters. Use 1 or 2.
     */
    public static void logCategoryInfo(Logger log, String msg, int level) {
       
        String categoryMarker = (level == 1 ? "#" : "=");
        StringBuffer line = new StringBuffer();
        line.append(StringUtils.repeat(categoryMarker, 3));
        line.append(" ");
        line.append(msg);
        line.append(" ");
       
        int remainingChars = 80 - msg.length();
        if (remainingChars > 0) {
            line.append(StringUtils.repeat(categoryMarker, remainingChars));
        }
        log.info(line.toString());
    }

    /**
     * Returns the field reflection object of the field of the given name.
     * This method will find fields of any scope in the given class and all superclasses.
     * @param theClass The class searched for the field
     * @param name The field name
     * @return The field reflection object or null if the field does not exist
     */
    public static Field getClassField(Class theClass, String name) {
       
        Field field = null;
        while (true) {
            try {
                field = theClass.getDeclaredField(name);
            }
            catch (Exception e) {
            }
           
            if (field != null) {
                return field;
            }
           
            if (theClass.getSuperclass() != null) {
                theClass = theClass = theClass.getSuperclass();
            }
            else {
                return null;
            }
        }
       
       
    }
   
    /**
     * Encodes some string to be safely used inside a JavaScript literal
     * @param str The string to encode
     * @return The encoded string
     */
    public static String encodeJS(String str) {
       
        // String delimiters are escaped with backslashes
        str = str.replaceAll("'", "\\\\'");
        str = str.replaceAll("\"", "\\\\\"");
       
        // Script tags are removed
        str = str.replaceAll("<script[^>]*>", "");
        str = str.replaceAll("</script[^>]*>", "");
       
        // Various types of linefeeds are replaced
        str = str.replaceAll("\n", "\\\\n");
        str = str.replaceAll("\u0085", "\\\\n");
        str = str.replaceAll("\u2028", "\\\\n");
        str = str.replaceAll("\u2029", "\\\\n");
       
        // Carriage return is removed
        str = str.replaceAll("\r", "");
       
        return str;
       
    }
   
    public static TemporaryFile createTempFile(String name, InputStream data) throws IOException {
        return new TemporaryFile(name, data, new File(System.getProperty("java.io.tmpdir")));
    }
   
    public static TemporaryFile createTempFile(String name) throws IOException {
        return new TemporaryFile(name, null, new File(System.getProperty("java.io.tmpdir")));
    }
}
TOP

Related Classes of de.innovationgate.utils.WGUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.