Java tutorial
/******************************************************************************* * 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.ByteArrayOutputStream; 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.ObjectOutputStream; 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.math.BigDecimal; import java.math.BigInteger; import java.net.MalformedURLException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.CharacterIterator; import java.text.DecimalFormat; import java.text.Normalizer; import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.Arrays; 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.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipInputStream; 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.io.output.NullOutputStream; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.commons.vfs2.Capability; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemException; import org.apache.commons.vfs2.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 { private static final Pattern PATTERN_ENCODE_SCRIPT_END = Pattern.compile("</script[^>]*>", Pattern.CASE_INSENSITIVE); private static final Pattern PATTERN_ENCODE_SCRIPT_START = Pattern.compile("<script[^>]*>", Pattern.CASE_INSENSITIVE); private static class PatchedGZIPInputStream extends java.util.zip.GZIPInputStream { public PatchedGZIPInputStream(InputStream in, int size) throws IOException { super(in, size); } public PatchedGZIPInputStream(InputStream in) throws IOException { super(in); } public int read(byte buf[], int off, int len) throws IOException { try { return super.read(buf, off, len); } catch (IOException e) { if (e.getMessage().indexOf("Corrupt GZIP trailer") != -1) { return -1; } else { throw e; } } } } /** * 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 = " #"; public static final Pattern JS_IDENTIFIER_CHARS = Pattern.compile("[A-Za-z0-9_$]"); public static final Pattern JS_IDENTIFIER_FIRSTCHARS = Pattern.compile("[A-Za-z_$]"); public static final Pattern ILLEGAL_HTML_CHARS_PATTERN = Pattern .compile("[\\x00-\\x08\\x0B-\\x0C\\x0E-\\x1F\\x7F]"); private static ObjectOutputStream _serialisation_test_stream; static { try { _serialisation_test_stream = new ObjectOutputStream(NullOutputStream.NULL_OUTPUT_STREAM); } catch (IOException e) { Logger.getLogger("wga.utils").error("Exception initializing serialisation test stream", e); } } 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(); } } @SuppressWarnings("rawtypes") 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) */ @SuppressWarnings("unchecked") 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"); /** * A normal decimal format including a thousands separator character */ public static final java.text.DecimalFormat DECIMALFORMAT_STANDARD = new java.text.DecimalFormat("#,##0"); /** * A decimal format for programmatic uses, guaranteeing to have no thousands separator and also no "default" fraction digits. * Existing fractions are given out up to the tenth digit */ public static final java.text.DecimalFormat DECIMALFORMAT_SYSTEM = 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"); /** * For {@link #encodeHTML(String, boolean, int)}, to let linefeeds be converted to <br> tags */ public static final int LINEFEEDS_CONVERT_TO_BR = 1; /** * For {@link #encodeHTML(String, boolean, int)}, to let linefeeds be kept untouched */ public static final int LINEFEEDS_KEEP = 2; /** * For {@link #encodeHTML(String, boolean, int)}, to let linefeeds be removed from the output */ public static final int LINEFEEDS_REMOVE = 3; /** * 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<? extends Object> col, String divider, ObjectFormatter formatter, boolean includeNulls) { if (divider == null) { divider = ""; } if (formatter == null) { formatter = DefaultObjectFormatter.getInstance(); } Iterator<? extends Object> elements = col.iterator(); StringBuilder output = new StringBuilder(); 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<? extends Object> col, String divider, ObjectFormatter formatter) { return serializeCollection(col, divider, formatter, false); } /** * Creates a List based on string, that contains substring tokens 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 tokens 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. * @param removeTokenStringDelimiter * If true and a string delimiter was given will remove delimiters that wrap whole tokens on output. So input string "'a','b'" will return strings "a" and "b" on deserialisation if the ' character was given as delimiter. * @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, boolean removeTokenStringDelimiter) { 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); token = processDeserializedToken(token, trimTokens, stringDelimiter, removeTokenStringDelimiter); col.add(token); startPos = nowPos + dividerLength; nowPos = searchString.indexOf(divider, startPos); } if (startPos <= colString.length()) { token = colString.substring(startPos); token = processDeserializedToken(token, trimTokens, stringDelimiter, removeTokenStringDelimiter); col.add(token); } return col; } private static String processDeserializedToken(String token, boolean trimTokens, Character stringDelimiter, boolean removeTokenStringDelimiter) { if (trimTokens) { token = token.trim(); } if (removeTokenStringDelimiter && stringDelimiter != null && token.startsWith(stringDelimiter.toString()) && token.endsWith(stringDelimiter.toString())) { token = token.substring(1, token.length() - 1); } return token; } /** * 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) { return deserializeCollection(colString, divider, trimTokens, stringDelimiter, false); } /** * 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(Collection<? extends Object> 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<Object> 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 you want line feeds to be converted to <br> tags, false if they should be removed * @return The HTML representation of the plain text. */ public static String encodeHTML(String input, boolean reduceToASCII, boolean useHTMLTags) { return encodeHTML(input, reduceToASCII, (useHTMLTags ? LINEFEEDS_CONVERT_TO_BR : LINEFEEDS_REMOVE)); } /** * 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 lineFeedTreatment * Way how line feeds are treated in the source. Use constants LINEFEEDS_... if you want them to be converted, ignored or removed * @return The HTML representation of the plain text. */ public static String encodeHTML(String input, boolean reduceToASCII, int lineFeedTreatment) { return encodeHTML(input, reduceToASCII, lineFeedTreatment, null); } public static String encodeHTML(String input, boolean reduceToASCII, int lineFeedTreatment, Set<Integer> additionalCharsToEncode) { if (input == null) { return ""; } if (additionalCharsToEncode == null) { additionalCharsToEncode = Collections.emptySet(); } StringReader in = new StringReader(input); StringBuffer out = new StringBuffer(); int ch; try { while ((ch = in.read()) != -1) { if (ch == '\n') { if (lineFeedTreatment == LINEFEEDS_CONVERT_TO_BR) { out.append("<br>"); } else if (lineFeedTreatment == LINEFEEDS_KEEP) { out.append("\n"); } } else if ((!reduceToASCII || ch < 127) && ch != '&' && ch != '<' && ch != '>' && ch != '"' && !additionalCharsToEncode.contains(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 */ @SuppressWarnings({ "unchecked", "rawtypes" }) 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<Object, Object> 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) { try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); byte[] digestedPwdBytes = messageDigest.digest(pwd.getBytes()); return Base64.encode(digestedPwdBytes); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm SHA-1 is not available", e); } } /** * 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(); } } } /** * Converts a byte array to a hexadecimal string * @param bytes The byte array * @return Hexadecimal representation of the given bytes */ public static String toHexString(byte[] bytes) { StringBuffer hex = new StringBuffer(); for (byte aByte : bytes) { String hexByte = java.lang.Integer.toHexString(0xFF & aByte); if (hexByte.length() == 1) { hex.append("0"); } hex.append(hexByte); } return hex.toString(); } /** * 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(), (Object[]) 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 { // First remove data URLs from code which may bloat the process html = WGUtils.strReplace(html, "src=\"data:", new ReplaceProcessor() { @Override public int replace(String text, int from, int to, Writer out) throws IOException { int linkEnd = text.indexOf("\"", to); out.write("src=\""); if (linkEnd != -1) { return linkEnd; } else { return text.length() - 1; } } }, true); html = WGUtils.strReplace(html, "href=\"data:", new ReplaceProcessor() { @Override public int replace(String text, int from, int to, Writer out) throws IOException { int linkEnd = text.indexOf("\"", to); out.write("href=\""); if (linkEnd != -1) { return linkEnd; } else { return text.length() - 1; } } }, true); // Convert to plaintext 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 <K extends Object, V extends Object> Map<String, V> extractMapByPrefix(Map<K, V> map, String prefix) { int prefixLen = prefix.length(); Map<String, V> result = new HashMap<>(); Iterator<K> 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 */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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 */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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 * null values are preserved. * * @param listOriginal * @return The list with all elements converted to their string representation */ public static List<String> toString(List<Object> listOriginal) { List<String> list = new ArrayList<String>(); Object elem; for (int i = 0; i < listOriginal.size(); i++) { elem = listOriginal.get(i); if (elem != null) { list.add(String.valueOf(elem)); } else { list.add(null); } } 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 */ @SuppressWarnings({ "unchecked", "rawtypes" }) 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 */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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<String> extractMessages(Throwable th) { List<String> msg = new ArrayList<String>(); while (th != null) { msg.add(th.getClass().getName() + " - " + th.getMessage()); if (th.getCause() != null && th.getCause() != th) { th = ((Exception) th).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; } /** * Returns the root cause throwable for the given throwable of a given type * @param e The throwable * @param throwableClass The class that is searched as cause * @return The root cause or null if none of the given class was found; */ @SuppressWarnings("unchecked") public static <T extends Throwable> T getRootCause(Throwable e, Class<T> throwableClass) { while (true) { if (throwableClass.isAssignableFrom(e.getClass())) { return (T) e; } if (e.getCause() != null) { e = e.getCause(); } else { return null; } } } /** * Returns a cause of the given throwable (or the throwable itself) which is of the given type. Returns null if no such cause was found. * @param e The exception * @param type The type of exception searched * @return The found exception or null */ public static <CauseType extends Throwable> CauseType getCauseOfType(Throwable e, Class<? extends CauseType> type) { while (e != null) { if (type.isAssignableFrom(e.getClass())) { @SuppressWarnings("unchecked") CauseType e2 = (CauseType) e; return e2; } e = e.getCause(); } return null; } /** * 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 * @deprecated As the minimum Java version for WGA is now at least 5 */ public static <K extends Object, V extends Object> Map<K, V> createSynchronizedMap() { return new ConcurrentHashMap<K, V>(); } /** * 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 * @deprecated since Java 5 is minimum dependency of OpenWGA */ 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 */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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 */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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 */ @SuppressWarnings({ "rawtypes" }) 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. */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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.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) { // Escape the backslash character itself str = str.replaceAll("\\\\", "\\\\\\\\"); // String delimiters are escaped with backslashes str = str.replaceAll("'", "\\\\'"); str = str.replaceAll("\"", "\\\\\""); // Script tags are removed str = PATTERN_ENCODE_SCRIPT_START.matcher(str).replaceAll(""); str = PATTERN_ENCODE_SCRIPT_END.matcher(str).replaceAll(""); // 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; } /** * Encodes some string to be safely used inside a JSON literal * JSON encoding has subtile differences from JavaScript (no encoding of single quotes). * @param str The string to encode * @return The encoded string */ public static String encodeJSON(String str) { // Escape the backslash character itself str = str.replaceAll("\\\\", "\\\\"); // String delimiters are escaped with backslashes str = str.replaceAll("\"", "\\\\\""); // Script tags are removed str = PATTERN_ENCODE_SCRIPT_START.matcher(str).replaceAll(""); str = PATTERN_ENCODE_SCRIPT_END.matcher(str).replaceAll(""); // 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", ""); // #00005192: encode Tabs str = str.replaceAll("\t", "\\\\t"); return str; } /** * Creates a temporary file with the given name and fills it with the data from the given stream. * If the input stream is no {@link ZipInputStream} it will be implicitly closed by this call. * @param name The file name * @param data The data for the file * @return A temporary file object * @throws IOException */ public static TemporaryFile createTempFile(String name, InputStream data) throws IOException { return new TemporaryFile(name, data, null); } /** * Creates an empty temporary file with the given name * @param name The file name * @return A temporary file object * @throws IOException */ public static TemporaryFile createTempFile(String name) throws IOException { return new TemporaryFile(name, null, null); } /** * performs a unicode normalization to NFC form (java.text.Normalizer.Form.NFC) for the given input * @param input The input string * @return the normalized or original value if already NFC form */ public static String normalizeUnicode(String input) { if (input != null && !Normalizer.isNormalized(input, Normalizer.Form.NFC)) { return Normalizer.normalize(input, Normalizer.Form.NFC); } return input; } /** * Converts arbitrary objects into their boolean counterpart * Boolean true are: * <ul> * <li>Boolean.TRUE * <li>All strings that {@link #stringToBoolean(String)} thinks are true * <li>The numbers 1 and -1 * </ul> * All other objects evaluate to the default given as argument. * @param obj Input object * @param def Default for returning on unconvertible objects (and null objects) * @deprecated Use {@link ConversionUtils#getBoolean(Object, boolean)} */ public static boolean toBoolean(Object obj, boolean def) { if (obj instanceof Boolean) { return (Boolean) obj; } if (obj instanceof String) { return stringToBoolean((String) obj); } else if (obj instanceof Number) { Number num = (Number) obj; return num.intValue() == 1 || num.intValue() == -1; } else { return def; } } /** * Converts arbitrary objects into their integer counterpart * Interpretable as integer are all {@link java.lang.Number} instances and all strings that represent representations of numbers, * also all other objects whose {@link #toString()} returns somthing that may be interpreted as number. * Non-interpretable objects and null return the default given as argument * @param obj Input object * @param def Default for returning on unconvertible objects (and null objects) * @deprecated Use {@link ConversionUtils#getInteger(Object, int)} */ public static Integer toInteger(Object obj, int def) { return ConversionUtils.getInteger(obj, def); } /** * This method tests if an object is serializable via the standard Java serialisation mechanism by attempting a serialisation * @param obj Object to test * @return true if the object is serialisable without errors */ public static boolean isSerializable(Object obj) { try { _serialisation_test_stream.writeObject(obj); return true; } catch (Throwable e) { return false; } } /** * Decodes an encoded URI sequence according to the given charset * This is able to decode URL-encoded characters but also leaves existing Non-ASCII characters intact (unlike Common HttpClients URIUtil) * @param uriCharSequence The URI sequence * @param charset The charset used to decode * @return The decoded string * @throws UnsupportedEncodingException * @throws MalformedURLException */ public static String decodeURI(CharSequence uriCharSequence, String charset) throws UnsupportedEncodingException, MalformedURLException { if (uriCharSequence == null) { return null; } String uri = uriCharSequence.toString(); int oi = 0; // output index ByteArrayOutputStream out = new ByteArrayOutputStream(); for (int i = 0; i < uri.length(); i++) { char c = uri.charAt(i); if (c == '%' && i + 2 <= uri.length()) { byte high = (byte) Character.digit((char) uri.charAt(++i), 16); byte low = (byte) Character.digit((char) uri.charAt(++i), 16); if (high == -1 || low == -1) { throw new MalformedURLException("Invalid escape pattern"); } byte aByte = (byte) ((high << 4) + low); out.write(aByte); } else if (c == '+') { out.write(' '); } else { byte[] bytes = String.valueOf(c).getBytes(charset); out.write(bytes, 0, bytes.length); } } return out.toString(charset); } /** * Zips a string input to compressed bytes * @param input The string * @return Output bytes as array, null if it is not zippable */ public static byte[] zipString(String input) { try { byte[] uncompressedBytes = input.getBytes("UTF-8"); return zip(uncompressedBytes); } catch (Exception e) { if ("true".equals(System.getProperty("de.innovationgate.wga.debug.zipping"))) { e.printStackTrace(); } return null; } } /** * Zips bytes to compressed bytes * @param uncompressedBytes Input * @throws IOException */ public static byte[] zip(byte[] uncompressedBytes) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream zos = new GZIPOutputStream(baos); zos.write(uncompressedBytes, 0, uncompressedBytes.length); zos.close(); return baos.toByteArray(); } /** * Unzips a string from bytes previously produced by {@link #zipString(String)} * @param input The bytes * @return The string, null if it is not unzippable */ public static String unzipString(byte[] input) { try { byte[] outBytes = unzip(input); return new String(outBytes, "UTF-8"); } catch (Exception e) { if ("true".equals(System.getProperty("de.innovationgate.wga.debug.zipping"))) { e.printStackTrace(); } return null; } } /** * Unzips compressed bytes previously produced by {@link #zip(byte[])@} * @param input The compressed bytes */ public static byte[] unzip(byte[] input) throws IOException { String unzipped = null; ByteArrayInputStream byteIn = new ByteArrayInputStream(input); PatchedGZIPInputStream zipIn = new PatchedGZIPInputStream(byteIn); ByteArrayOutputStream out = new ByteArrayOutputStream(); int numBytesRead = 0; byte[] tempBytes = new byte[1024]; while ((numBytesRead = zipIn.read(tempBytes, 0, tempBytes.length)) != -1) { out.write(tempBytes, 0, numBytesRead); } byte[] outBytes = out.toByteArray(); out.close(); zipIn.close(); byteIn.close(); return outBytes; } /** * Removes C0 control characters that are invalid in XML 1.0 and not even allowed as escaped entities from the string * This method removes all characters from U+0000 to U+001F except U+0009, U+000A, U+000D. * @param str Input string * @return String with invalid characters removed */ public static String toValidXmlString(String str) { return ILLEGAL_HTML_CHARS_PATTERN.matcher(str).replaceAll(""); } /** * Converts a {@link Properties} object to a {@link Map} with string generics. * Although {@link Properties} is only able to store String keys and values it inherits from Map<Object,Object> for funky reasons. * This method is a toolie to convert {@link Properties} to be usable as String map * @param props The properties object */ public static Map<String, String> propertiesToStringMap(Properties props) { Map<String, String> map = new HashMap<String, String>(); for (Object key : props.keySet()) { map.put((String) key, props.getProperty((String) key)); } return map; } /** * Removes all characters from a string that are not legal as JavaScript identifier * Interprets an underscore as the next character being forced to upper case. * The upper case rules, that are enforced when param enforceUpperCaseRules is true are: * <ul> * <li>Non-alphabetic characters at the beginning are stripped off * <li>The first alphabetic character is uppercase * <li>An alphabetic character preceded by an underscore (which is removed) will also be uppercase * <li>All other alphabetic characters are lowercase * </ul> * @param txt The string * @param enforceUpperCaseRules If true enforces the upper case rules described above, meant to transfor, a lowercase design reference to an Object Identifier with cases in an reversible way. * In that case the created string can be reverted to the original by using {@link #fromJSIdentifier(String)}. * @return The string converted to a valid JS identifier */ public static String toJSIdentifier(String txt, boolean enforceUpperCaseRules) { StringBuilder out = new StringBuilder(); // Flags for enforceUpperCaseRules boolean upperCase = true; // First character is uppercase boolean startCharacter = true; for (int i = 0; i < txt.length(); i++) { Character c = txt.charAt(i); if (enforceUpperCaseRules) { // Must start with a letter if (startCharacter && !Character.isLetter(c)) { continue; } // Convert letters to uppercase, depending on flag if (Character.isLetter(c)) { if (upperCase) { c = Character.toUpperCase(c); upperCase = false; } else { c = Character.toLowerCase(c); } } // Trigger the next letter to be uppercase else if (c == '_') { upperCase = true; continue; } } Pattern p = (startCharacter ? JS_IDENTIFIER_FIRSTCHARS : JS_IDENTIFIER_CHARS); if (p.matcher(c.toString()).matches()) { out.append(c); startCharacter = false; } else if (enforceUpperCaseRules) { throw new IllegalArgumentException( "This string cannot be transformed to a reversible JS identifier because it contains invalid characters: " + txt); } } return out.toString(); } /** * Reconstructs the original string from the output of {@link #toJSIdentifier(String, boolean)} with enforced upper case rules. * @param txt A string created by {@link #toJSIdentifier(String)} with enforced upper case rules * @return The original string */ public static String fromJSIdentifier(String txt) { StringBuilder out = new StringBuilder(); int newWordIdx = -1; for (int i = 0; i < txt.length(); i++) { Character c = txt.charAt(i); newWordIdx++; if (Character.isUpperCase(c)) { if (newWordIdx != 0) { out.append("_"); } c = Character.toLowerCase(c); } else if (c == '.') { newWordIdx = -1; } out.append(c); } return out.toString(); } /** * Removes all characters from a string that are not legal as JavaScript identifier * Interprets an underscore as the next character being forced to upper case. * @param txt The string * @return The string converted to a valid JS identifier */ public static String toJSIdentifier(String txt) { return toJSIdentifier(txt, false); } /** * Convenience function to create a list from elements * @param elements The elements */ @SafeVarargs public static <Type> List<Type> list(Type... elements) { return new ArrayList<Type>(Arrays.<Type>asList(elements)); } /** * Convenience function to create a list from elements, overtaking the elements of the first parameter list * The parameter lists elements will be first; * @param list The parameter lister * @param elements The elements */ @SafeVarargs public static <Type> List<Type> list(List<Type> list, Type... elements) { List<Type> newList = new ArrayList<Type>(list); newList.addAll(Arrays.asList(elements)); return newList; } /** * Converts any number to a BigDecimal, suited for comparison and (monetary) arithmetic. * This class uses conversions that avoid rounding errors by forced conversion for the * Number types from JRE. Fallback for other types is to convert them to Double. * @param n The number */ public static BigDecimal toBigDecimal(Number n) { if (n instanceof BigDecimal) { return (BigDecimal) n; } if (n instanceof Long) { return new BigDecimal((Long) n); } else if (n instanceof Integer) { return new BigDecimal((Integer) n); } else if (n instanceof Float) { return new BigDecimal((Float) n); } else if (n instanceof Double) { return new BigDecimal((Double) n); } else if (n instanceof Short) { return new BigDecimal((Short) n); } else if (n instanceof Byte) { return new BigDecimal((Byte) n); } else if (n instanceof BigInteger) { return new BigDecimal((BigInteger) n); } else if (n instanceof AtomicInteger) { return new BigDecimal(n.intValue()); } else if (n instanceof AtomicLong) { return new BigDecimal(n.longValue()); } else { return new BigDecimal(n.doubleValue()); } } }