com.day.text.Text Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2012 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.day.text;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.Vector;
/**
* This class holds a collection of useful string operations that are not
* available in java.
*/
public final class Text {
/**
* The default format pattern used in strftime() if no pattern
* parameter has been supplied. This is the default format used to format
* dates in Communiqué 2
*/
public static final String DEFAULT_DATE_FORMAT_PATTERN = "dd.MM.yyyy HH:mm:ss";
/**
* Common used DateFormat implementation. When the supplied formatting
* pattern has been translated it is applied to this formatter and then
* executed. Both steps occur in a synchronized section, such that no
* two threads disturb each other.
*
* - A single instance of the formatter is used to prevent the object
* creation overhead. But then how much is this ?
*
- Using one static formatter for each thread may prove to give even
* more overhead given all those synchronized blocks waiting for each
* other during real formatting. But then how knows ?
*
*
* This formatter must always be used synchronized as follows :
*
synchronized (dateFormatter) {
dateFormatter.applyPattern( ... );
dateFormatter.setTimezone( ... );
String result = dateFormatter.format( ... );
}
*
*
* To parse date and time strings , the formatter is used as follows :
*
synchronized (dateFormatter) {
dateFormatter.applyPattern( ... );
dateFormatter.setTimezone( ... );
try {
Date result = dateFormatter.parse(dateString);
} catch (ParseException pe) {
// handle exception
}
}
*
*
*/
private static final SimpleDateFormat dateFormatter = new SimpleDateFormat();
/**
* The UTC timezone
*/
public static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone("UTC");
/** format for RFC 1123 date string -- "Sun, 06 Nov 1994 08:49:37 GMT" */
private final static SimpleDateFormat rfc1123Format =
new SimpleDateFormat("EEE, dd MMM yyyyy HH:mm:ss z", Locale.US);
static {
rfc1123Format.setTimeZone(TIMEZONE_UTC);
}
/**
* The local timezone
*/
public static final TimeZone TIMEZONE_LOCAL = TimeZone.getDefault();
/**
* Empty result
*/
private final static String[] empty = new String[0];
/**
* MD4 hash algorithm name.
*/
private static final String MD4_HASH = "md4";
/**
* NTLM hash that requires UnicodeLittleUnmarked encoding.
*/
private static final String NTLM_HASH = "ntlm";
/**
* avoid instantiation
*/
private Text() {
}
/**
* returns an array of strings decomposed of the original string, split at
* every occurance of 'ch'. if 2 'ch' follow each other with no intermediate
* characters, empty "" entries are avoided.
*
* @param str the string to decompose
* @param ch the character to use a split pattern
* @return an array of strings
*
* @deprecated use {@link #explode(String, int)}
*/
public static String[] split(String str, int ch) {
return explode(str,ch,false);
}
/**
* returns an array of strings decomposed of the original string, split at
* every occurance of 'ch'.
* @param str the string to decompose
* @param ch the character to use a split pattern
* @param respectEmpty if true
, empty elements are generated
* @return an array of strings
*
* @deprecated use {@link #explode(String, int, boolean)}
*/
public static String[] split(String str, int ch, boolean respectEmpty) {
return explode(str, ch, respectEmpty);
}
/**
* returns an array of strings decomposed of the original string, split at
* every occurance of 'ch'. if 2 'ch' follow each other with no intermediate
* characters, empty "" entries are avoided.
*
* @param str the string to decompose
* @param ch the character to use a split pattern
* @return an array of strings
*/
public static String[] explode(String str, int ch) {
return explode(str,ch,false);
}
/**
* returns an array of strings decomposed of the original string, split at
* every occurance of 'ch'.
* @param str the string to decompose
* @param ch the character to use a split pattern
* @param respectEmpty if true
, empty elements are generated
* @return an array of strings
*/
public static String[] explode(String str, int ch, boolean respectEmpty) {
if (str == null) {
return empty;
}
Vector strings = new Vector();
int pos;
int lastpos = 0;
// add snipples
while ((pos = str.indexOf(ch, lastpos)) >= 0) {
if (pos-lastpos>0 || respectEmpty)
strings.add(str.substring(lastpos, pos));
lastpos = pos+1;
}
// add rest
if (lastpos < str.length()) {
strings.add(str.substring(lastpos));
} else if (respectEmpty && lastpos==str.length()) {
strings.add("");
}
// return stringarray
return (String[]) strings.toArray(new String[strings.size()]);
}
/**
* Concatenates all strings in the string array using the specified delimiter.
* @param arr
* @param delim
* @return the concatenated string
*/
public static String implode(String[] arr, String delim) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
buf.append(delim);
}
buf.append(arr[i]);
}
return buf.toString();
}
/**
* Replaces all occurences of oldString
in text
* with newString
.
*
* @param text
* @param oldString old substring to be replaced with newString
* @param newString new substring to replace occurences of oldString
* @return a string
*/
public static String replace(String text, String oldString, String newString) {
if (text == null || oldString == null || newString == null) {
throw new IllegalArgumentException("null argument");
}
int pos = text.indexOf(oldString);
if (pos == -1) {
return text;
}
int lastPos = 0;
StringBuffer sb = new StringBuffer(text.length());
while (pos != -1) {
sb.append(text.substring(lastPos, pos));
sb.append(newString);
lastPos = pos + oldString.length();
pos = text.indexOf(oldString, lastPos);
}
if (lastPos < text.length()) {
sb.append(text.substring(lastPos));
}
return sb.toString();
}
/**
* Returns the name part of the path
*
* @param path the path
* @return the name part
*/
public static String getName(String path) {
if (path == null) {
return null;
} else {
return path.substring(path.lastIndexOf('/') + 1);
}
}
/**
* Returns the name part of the path, delimited by the given delim
*
* @param path the path
* @param delim the delimiter
* @return the name part
*/
public static String getName(String path, char delim) {
if (path == null) {
return null;
} else {
return path.substring(path.lastIndexOf(delim) + 1);
}
}
/**
* Same as {@link #getName(String)} but adding the possibility
* to pass paths that end with a trailing '/'
*
* @param path the path
* @param ignoreTrailingSlash if true
a trailing slash is ignored
* @return the name part
*
* @see #getName(String)
*/
public static String getName(String path, boolean ignoreTrailingSlash) {
if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
path = path.substring(0, path.length()-1);
}
return getName(path);
}
/**
* Returns the namespace prefix of the given qname
. If the
* prefix is missing, an empty string is returned. Please note, that this
* method does not validate the name or prefix.
*
* the qname has the format: qname := [prefix ':'] local;
*
* @param qname a qualified name
* @return the prefix of the name or "".
*
* @see #getLocalName(String)
*
* @throws NullPointerException if qname
is null
*/
public static String getNamespacePrefix(String qname) {
int pos = qname.indexOf(':');
return pos >=0 ? qname.substring(0, pos) : "";
}
/**
* Returns the local name of the given qname
. Please note, that
* this method does not validate the name.
*
* the qname has the format: qname := [prefix ':'] local;
*
* @param qname a qualified name
* @return the localname
*
* @see #getNamespacePrefix(String)
*
* @throws NullPointerException if qname
is null
*/
public static String getLocalName(String qname) {
int pos = qname.indexOf(':');
return pos >=0 ? qname.substring(pos+1) : qname;
}
/**
* compares to handles lexigographically with one exception: the '/'
* character is always considered smaller than all other chars. this results
* in a ordering, in which the parent pages come first (it's about 6 times
* slower than the string impl. of compareTo).
* example (normal string compare):
* - /foo
*
- /foo-bar
*
- /foo/bar
*
* example (this handle compare):
* - /foo
*
- /foo/bar
* - /foo-bar
*
*
* @param h1 the first handle
* @param h2 the second handle
* @return the return is positive, if the first handle is bigger than the
* second; negative, if the first handle is smaller than the second;
* and zero, if the two handles are equal.
*/
public static int compareHandles(String h1, String h2) {
char[] ca1=h1.toCharArray(); // this is faster, than a .charAt everytime
char[] ca2=h2.toCharArray();
int n= ca1.length < ca2.length ? ca1.length : ca2.length;
int i=0;
while (iparent as parent directory rather than a base
* handle. if further respects full qualified uri's.
*
examples:
*
* parent | path | result
* ----------+----------+------------
* "" | "" | /
* /foo | "" | /foo
* "" | /foo | /foo
* "." | foo | foo
* /foo/bar | bla | /foo/bar/bla
* /foo/bar | /bla | /bla
* /foo/bar | ../bla | /foo/bla
* /foo/bar | ./bla | /foo/bar/bla
* foo | bar | foo/bar
* c:/bla | gurk | c:/bla/gurk
* /foo | c:/bar | c:/bar
*
*
* @param parent the base handle
* @param path the path
*/
public static String fullFilePath(String parent, String path) {
if (parent == null) parent = "";
// compose source string
char[] source;
if (path == null || path.length() == 0) {
// only parent path
source = parent.toCharArray();
} else if (path.charAt(0) == '/' || path.indexOf(':') >= 0) {
// absolute path, ignore base
source = path.toCharArray();
} else {
// relative to parent
source = (parent + "/./" + path).toCharArray();
}
return makeCanonicalPath(source);
}
/**
* returns a full path.
* if base is empty, '/' is assumed
* if base and path are relative, a relative path will be generated.
*
examples:
*
* base | path | result
* ----------+----------+------------
* "" | "" | /
* /foo | "" | /foo
* "" | /foo | /foo
* "." | foo | foo
* /foo/bar | bla | /foo/bla
* /foo/bar | /bla | /bla
* /foo/bar | ../bla | /bla
* /foo/bar | ./bla | /foo/bla
* foo | bar | bar
*
*
* @param base the base handle
* @param path the path
*/
public static String fullPath(String base, String path) {
if (base == null) base = "";
// compose source string
char[] source;
if (path == null || path.length() == 0) {
// only base path
source = base.toCharArray();
} else if (path.charAt(0) == '/') {
// absolute path, ignore base
source = path.toCharArray();
} else {
// relative to base
source = (base + "/../" + path).toCharArray();
}
return makeCanonicalPath(source);
}
/**
* Make a path canonical. This is a shortcut for
*
* Text.makeCanonicalPath(new StringBuffer(path));
*
* @param path path to make canonical
*/
public static String makeCanonicalPath(String path) {
return makeCanonicalPath(path.toCharArray());
}
/**
* make a cannonical path. removes all /./ and /../ and multiple slashes.
* @param source the input source
* @return a string containing the cannonical path
*
* @deprecated use {@link #makeCanonicalPath(char[])} instead
*/
public static String makeCanonicalPath(StringBuffer source) {
return makeCanonicalPath(source.toString().toCharArray());
}
/**
* make a cannonical path. removes all /./ and /../ and multiple slashes.
* @param source the input source
* @return a string containing the cannonical path
*/
public static String makeCanonicalPath(char[] source) {
// remove/resolve .. and .
int dst = 0, pos = 0, slash = 0, dots = 0, len = source.length;
char last = 0;
int[] slashes = new int[1024];
slashes[0] = (len > 0 && source[0] == '/') ? 0 : -1;
while (pos < len) {
char ch = source[pos++];
switch (ch) {
case '/':
// ignore multiple slashes
if (last == '/') continue;
// end of segment
if (dots == 1) {
// ignore
dst = slashes[slash];
} else if (dots == 2) {
// backtrack
if (--slash < 0) slash = 0;
dst = slashes[slash];
} else {
// remember slash position
slashes[++slash] = dst;
}
dots = 0;
break;
case '.':
// count dots
dots++;
break;
default:
// invalidate dots
dots = 3;
}
last = ch;
if (dst >= 0) source[dst] = ch;
dst++;
}
// check dots again
if (dots == 1) {
dst = slashes[slash];
} else if (dots == 2) {
if (slash > 0) {
slash--;
}
dst = slashes[slash];
}
// truncate result
return (dst == 0) ? "/" : new String(source, 0, dst);
}
/**
* Determines, if two handles are sister-pages, that meens, if they
* represent the same hierarchic level and share the same parent page.
* @param h1 first handle
* @param h2 second handle
* @return true if on same level, false otherwise
*/
public static boolean isSibling(String h1, String h2) {
int pos1 = h1.lastIndexOf('/');
int pos2 = h2.lastIndexOf('/');
return (pos1==pos2 && pos1>=0 && h1.regionMatches(0,h2,0,pos1));
}
/**
* Determines if the descendant
handle is hierarchical a
* descendant of handle
.
*
* /content/playground/en isDescendantOf /content/playground
* /content/playground/en isDescendantOf /content
* /content/playground/en isNOTDescendantOf /content/designground
* /content/playground/en isNOTDescendantOf /content/playground/en
*
*
* @param handle the current handle
* @param descendant the potential descendant
* @return true
if the descendant
is a descendant;
* false
otherwise.
* @since gumbear
*/
public static boolean isDescendant(String handle, String descendant) {
return !handle.equals(descendant) &&
descendant.startsWith(handle) &&
(descendant.charAt(handle.length())=='/' || handle.equals("/"));
}
/**
* Determines if the descendant
handle is hierarchical a
* descendant of handle
or equal to it.
*
* /content/playground/en isDescendantOrEqualOf /content/playground
* /content/playground/en isDescendantOrEqualOf /content
* /content/playground/en isDescendantOrEqualOf /content/playground/en
* /content/playground/en isNOTDescendantOrEqualOf /content/designground
*
*
* @param handle the current handle
* @param descendant the potential descendant
* @return true
if the descendant
is a descendant
* or equal; false
otherwise.
* @since gumbear
*/
public static boolean isDescendantOrEqual(String handle, String descendant) {
return handle.equals(descendant) ||
(descendant.startsWith(handle) &&
(descendant.charAt(handle.length())=='/') || handle.equals("/"));
}
/**
* Returns the label of a handle
* @param handle the handle
* @return the label
* @deprecated use {@link #getName(String)}
*/
public static String getLabel(String handle) {
return getName(handle);
}
/**
* Returns the label of a string
* @param handle the string
* @param delim the delimiter
* @return the label
*
* @deprecated use {@link #getName(String, char)}
*/
public static String getLabel(String handle, char delim) {
return getName(handle, delim);
}
/**
* used for the md5
*/
public final static char[] hexTable="0123456789abcdef".toCharArray();
/**
* Calculate an MD5 hash of the string given.
* @param data the data to encode
* @param enc the character encoding to use
* @return a hex encoded string of the md5 digested input
* @throws UnsupportedEncodingException if the given encoding is not supported
*/
public static String md5(String data, String enc)
throws UnsupportedEncodingException {
try {
return digest("MD5", data.getBytes(enc));
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 digest not available???");
}
}
/**
* Calculate an MD5 hash of the string given.
* @param data the data to encode
* @return a hex encoded string of the md5 digested input
*
* @deprecated {@link #md5(String, String)}
*/
public static String md5(String data) {
try {
return digest("MD5", data.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 digest not available???");
}
}
/**
* Digest the plain string using the given algorithm.
*
* @param algorithm The alogrithm for the digest. This algorithm must be
* supported by the MessageDigest class.
* @param data The plain text String to be digested.
* @param enc The character encoding to use
*
* @return The digested plain text String represented as Hex digits.
*
* @throws NoSuchAlgorithmException if the desired algorithm is not supported by
* the MessageDigest class.
* @throws UnsupportedEncodingException if the encoding is not supported
*/
public static String digest(String algorithm, String data, String enc)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
if (algorithm.equalsIgnoreCase(NTLM_HASH)) {
/* NTLM hash always takes UnicodeLittleUnmarked encoding */
return digest(MD4_HASH, data.getBytes("UnicodeLittleUnmarked"));
}
return digest(algorithm, data.getBytes(enc));
}
/**
* Digest the plain string using the given algorithm.
*
* @param algorithm The alogrithm for the digest. This algorithm must be
* supported by the MessageDigest class.
* @param data The plain text String to be digested.
*
* @return The digested plain text String represented as Hex digits.
*
* @throws NoSuchAlgorithmException if the desired algorithm is not supported by
* the MessageDigest class.
*
* @deprecated use {@link #digest(String, String, String)}
*/
public static String digest(String algorithm, String data)
throws NoSuchAlgorithmException {
return digest(algorithm, data.getBytes());
}
/**
* Digest the plain string using the given algorithm.
*
* @param algorithm The alogrithm for the digest. This algorithm must be
* supported by the MessageDigest class.
* @param data the data to digest with the given algorithm
*
* @return The digested plain text String represented as Hex digits.
*
* @throws NoSuchAlgorithmException if the desired algorithm is not supported by
* the MessageDigest class.
*/
public static String digest(String algorithm, byte[] data)
throws NoSuchAlgorithmException {
byte[] digest;
if (algorithm.equalsIgnoreCase(MD4_HASH)) {
digest = MD4.digest(data);
} else {
MessageDigest md = MessageDigest.getInstance(algorithm);
digest = md.digest(data);
}
StringBuffer res = new StringBuffer(digest.length * 2);
for (int i=0; i>4)&15]);
res.append(hexTable[b&15]);
}
return res.toString();
}
/**
* Returns the nth relative parent of the handle, where n=level.
* Example:
*
* Text.getRelativeParent("/en/home/index/about", 1) == "/en/home/index"
*
*
* @param handle the handle of the page
* @param level the level of the parent
*/
public static String getRelativeParent(String handle, int level) {
int idx = handle.length();
while (level > 0) {
idx = handle.lastIndexOf('/',idx-1);
if (idx < 0) {
return "";
}
level--;
}
return handle.substring(0,idx);
}
/**
* Returns the nth absolute parent of the handle, where n=level.
*
Example:
*
* Text.getAbsoluteParent("/en/home/index/about", 1) == "/en/home"
*
*
* @param handle the handle of the page
* @param level the level of the parent
*/
public static String getAbsoluteParent(String handle, int level) {
int idx = 0;
int len = handle.length();
while (level >= 0 && idx=0 ? "" : handle.substring(0,idx);
}
/**
* The list of characters that are not encoded by the escape()
* and unescape()
methods. They contains the characters as
* defined 'unreserved' in section 2.3 of the RFC 2396 'URI genric syntax':
*
*
* unreserved = alphanum | mark
* mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
*
*/
public static BitSet URISave;
/**
* Same as {@link #URISave} but also contains the '/'
*/
public static BitSet URISaveEx;
static {
URISave = new BitSet(256);
int i;
for (i = 'a'; i <= 'z'; i++) {
URISave.set(i);
}
for (i = 'A'; i <= 'Z'; i++) {
URISave.set(i);
}
for (i = '0'; i <= '9'; i++) {
URISave.set(i);
}
URISave.set('-');
URISave.set('_');
URISave.set('.');
URISave.set('!');
URISave.set('~');
URISave.set('*');
URISave.set('\'');
URISave.set('(');
URISave.set(')');
URISaveEx = (BitSet) URISave.clone();
URISaveEx.set('/');
}
/**
* Does an URL encoding of the string
using the
* escape
character. The characters that don't need encoding
* are those defined 'unreserved' in section 2.3 of the 'URI genric syntax'
* RFC 2396, but without the escape character.
*
* @param string the string to encode.
* @param escape the escape character.
* @return the escaped string
*
* @throws NullPointerException if string
is null
.
*/
public static String escape(String string, char escape) {
return escape(string, escape, false);
}
/**
* Does an URL encoding of the string
using the
* escape
character. The characters that don't need encoding
* are those defined 'unreserved' in section 2.3 of the 'URI genric syntax'
* RFC 2396, but without the escape character. If isPath
is
* true
, additionally the slash '/' is ignored, too.
*
* @param string the string to encode.
* @param escape the escape character.
* @param isPath if true
, the string is treated as path
*
* @return the escaped string
*
* @throws NullPointerException if string
is null
.
*/
public static String escape(String string, char escape, boolean isPath) {
try {
BitSet validChars = isPath ? URISaveEx : URISave;
byte[] bytes = string.getBytes("utf-8");
StringBuffer out = new StringBuffer(bytes.length);
for (int i = 0; i < bytes.length; i++) {
int c = bytes[i]&0xff;
if (validChars.get(c) && c!=escape) {
out.append((char)c);
} else {
out.append(escape);
out.append(hexTable[(c>>4) & 0x0f]);
out.append(hexTable[(c ) & 0x0f]);
}
}
return out.toString();
} catch (UnsupportedEncodingException e) {
throw new InternalError(e.toString());
}
}
/**
* Escapes all character that have specific meaning in XMl/HTML, namely
* '<', '>', '"', '&' by their isolating encoding aequivalents
* "<", ">", """, "&"
*
* @param string the string to escape
* @return the escaped string or null
if the input string was
* null
*/
public static String escapeXml(String string) {
if (string == null) {
return null;
}
final int len = string.length();
if (len == 0) {
return string;
}
StringBuffer ret = new StringBuffer(len);
for (int i=0; i':
ret.append(">");
break;
default:
ret.append(c);
}
}
return ret.toString();
}
/**
* Does a URL encoding of the string
. The characters that
* don't need encoding are those defined 'unreserved' in section 2.3 of
* the 'URI genric syntax' RFC 2396.
*
* @param string the string to encode
* @return the escaped string
*
* @throws NullPointerException if string
is null
.
*/
public static String escape(String string) {
return escape(string, '%');
}
/**
* Does a URL encoding of the path
. The characters that
* don't need encoding are those defined 'unreserved' in section 2.3 of
* the 'URI genric syntax' RFC 2396. In contrast to the
* {@link #escape(String)} method, not the entire path string is escaped,
* but every individual part (i.e. the slashes are not escaped).
*
* @param path the path to encode
* @return the escaped path
*
* @throws NullPointerException if path
is null
.
*/
public static String escapePath(String path) {
return escape(path, '%', true);
}
/**
* Does a URL decoding of the string
using the
* escape
character. Please note that in opposite to the
* {@link java.net.URLDecoder} it does not transform the + into spaces.
* @param string the string to decode
* @param escape the escape character
* @return the decoded string
*
* @throws NullPointerException if string
is null
.
* @throws ArrayIndexOutOfBoundsException if not enough character follow an
* escape character
* @throws IllegalArgumentException if the 2 characters following the escape
* character do not represent a hex-number.
*/
public static String unescape(String string, char escape) {
ByteArrayOutputStream out = new ByteArrayOutputStream(string.length()*2);
int lastPos=0;
int pos;
while ((pos=string.indexOf(escape, lastPos))>=0) {
try {
out.write(string.substring(lastPos, pos).getBytes("utf-8"));
} catch (IOException e) {
throw new InternalError(e.toString());
}
lastPos = pos+3;
if (lastPos<=string.length()) {
try {
out.write(Integer.parseInt(string.substring(pos+1, lastPos),16));
} catch (NumberFormatException e) {
throw new IllegalArgumentException();
}
} else {
// not enough characters, ignore rest
}
}
if (lastPos>=0 && lastPos<=string.length()) {
try {
out.write(string.substring(lastPos).getBytes("utf-8"));
} catch (IOException e) {
throw new InternalError(e.toString());
}
}
try {
return new String(out.toByteArray(), "utf-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError(e.toString());
}
}
/**
* Does a URL decoding of the string
. Please note that in
* opposite to the {@link java.net.URLDecoder} it does not transform the +
* into spaces.
*
* @param string the string to decode
* @return the decoded string
*
* @throws NullPointerException if string
is null
.
* @throws ArrayIndexOutOfBoundsException if not enough character follow an
* escape character
* @throws IllegalArgumentException if the 2 characters following the escape
* character do not represent a hex-number.
*/
public static String unescape(String string) {
return unescape(string, '%');
}
/**
* Returns a stringified date accoring to the date format specified in
* RTF1123. this is of the form: "Sun, 06 Nov 1994 08:49:37 GMT"
*/
public static String dateToRfc1123String(Date date) {
synchronized (rfc1123Format) {
return rfc1123Format.format(date);
}
}
/**
* Implements a date formatting routine supporting (a subset) of the POSIX
* strftime()
function.
*
* @param date The date value to be formatted
* @param formatPattern The pattern used to format the date. This pattern
* supports a subset of the pattern characters of the POSIX
* strftime()
function. If this pattern is empty or
* null
the default pattern
* dd.MM.yyyy HH:mm:ss
is used.
* @param zone Defines for which time zone the date should be outputted. If
* this parameter is null
, then the local time zone is taken.
*
* @return the formatted date as a String.
*/
public static String strftime(Date date, String formatPattern, TimeZone zone) {
// Check whether to apply default format
if (formatPattern == null || formatPattern.length() == 0) {
formatPattern = DEFAULT_DATE_FORMAT_PATTERN;
} else {
formatPattern = convertFormat(formatPattern);
}
// check zone
if (zone == null) zone = TIMEZONE_LOCAL;
// Reuse the global SimpleDateFormat synchronizing on it to prevent
// multiple tasks from interfering with each other
synchronized(dateFormatter) {
dateFormatter.applyPattern(formatPattern);
dateFormatter.setTimeZone(zone);
return dateFormatter.format(date);
}
}
/**
* Implements a date formatting routine supporting (a subset) of the POSIX
* strftime()
function.
*
* @param date The date value to be formatted
* @param formatPattern The pattern used to format the date. This pattern
* supports a subset of the pattern characters of the POSIX
* strftime()
function. If this pattern is empty or
* null
the default pattern
* dd.MM.yyyy HH:mm:ss
is used.
* @param asUTC Defines whether to interpret the date as belong to the UTC
* time zone or the local time zone.
*/
public static String strftime(Date date, String formatPattern, boolean asUTC) {
return strftime(date, formatPattern, asUTC ? TIMEZONE_UTC : TIMEZONE_LOCAL);
}
/**
* Implements a date formatting routine supporting (a subset) of the POSIX
* strftime()
function. The default pattern
* dd.MM.yyyy HH:mm:ss
is used to format the date.
*
* @param date The date value to be formatted
* @param asUTC Defines whether to interpret the date as belong to the UTC
* time zone or the local time zone.
*/
public static String strftime(Date date, boolean asUTC) {
return strftime(date, null, asUTC);
}
/**
* Implements a date formatting routine supporting (a subset) of the POSIX
* strftime()
function. The default pattern
* dd.MM.yyyy HH:mm:ss
is used to format the date, which is
* interpreted to be in the local time zone.
*
* @param date The date value to be formatted
*/
public static String strftime(Date date) {
return strftime(date, null, false);
}
/**
* Parses the date string based on the format pattern which is in the
* format used by the Java platfrom SimpleDateFormat class.
*
* @param dateString The date string to be parsed
* @param formatPattern the pattern to use for parsing. If null
* or empty, the same default pattern is used as with
* {@link #strftime(Date, String, boolean)}, namely
* dd.MM.yyyy HH:mm:ss
.
*
* @throws ParseException if the date string cannot be parsed accordinung
* to the format pattern.
*/
public static Date parseDate(String dateString, String formatPattern)
throws ParseException {
return parseDate(dateString, formatPattern, false);
}
/**
* Parses the date string based on the format pattern which is in the
* format used by the Java platfrom SimpleDateFormat class.
*
* @param dateString The date string to be parsed
* @param formatPattern the pattern to use for parsing. If null
* or empty, the same default pattern is used as with
* {@link #strftime(Date, String, boolean)}, namely
* dd.MM.yyyy HH:mm:ss
.
* @param isUTC if true
the date string is considered in UTC,
* otherwise the default timezone of the host is used.
*
* @throws ParseException if the date string cannot be parsed accordinung
* to the format pattern.
*/
public static Date parseDate(String dateString, String formatPattern,
boolean isUTC)
throws ParseException {
synchronized (dateFormatter) {
dateFormatter.applyPattern(formatPattern);
if (isUTC) {
dateFormatter.setTimeZone(TIMEZONE_UTC);
} else {
dateFormatter.setTimeZone(TimeZone.getDefault());
}
return dateFormatter.parse(dateString);
}
}
/**
* Parses the date string based on the format pattern which is in the
* default format dd.MM.yyyy HH:mm:ss
.
*
* @param dateString The date string to be parsed
*
* @throws ParseException if the date string cannot be parsed accordinung
* to the format pattern.
*/
public static Date parseDate(String dateString) throws ParseException {
return parseDate(dateString, DEFAULT_DATE_FORMAT_PATTERN, false);
}
/**
* Parses the date string based on the format pattern which is in the
* default format dd.MM.yyyy HH:mm:ss
.
*
* @param dateString The date string to be parsed
* @param isUTC if true
the date string is considered in UTC,
* otherwise the default timezone of the host is used.
*
*
* @throws ParseException if the date string cannot be parsed accordinung
* to the format pattern.
*/
public static Date parseDate(String dateString, boolean isUTC)
throws ParseException {
return parseDate(dateString, DEFAULT_DATE_FORMAT_PATTERN, isUTC);
}
//--------- sprintf() formatting constants ---------------------------------
/** left justified - '-' flag */
private static final int FLAG_LJ = 1;
/** always show sign - '+' flag */
private static final int FLAG_SI = 2;
/** space placeholder for omitted plus sign - ' ' flag, ignore if SI */
private static final int FLAG_SP = 3;
/** zero padded if right aligned - '0' flag, ignore if LJ */
private static final int FLAG_ZE = 4;
/**
* alternate format - '#' flag :
* SI - incr. precision to have zero as first char
* x - prefix '0x'
* X - prefix '0X'
* eEf - always show decimal point, omit trailing zeroes
* gG - always show decimal point, show trailing zeroes
*/
private static final int FLAG_AL = 5;
/** interpret ints as short - 'h' size */
private static final int FLAG_SHORT = 8;
/** interpret ints as long - 'l' size */
private static final int FLAG_LONG = 9;
/** waiting for format */
private static final int PARSE_STATE_NONE = 0;
/** parsing flags */
private static final int PARSE_STATE_FLAGS = 1;
/** parsing wdth */
private static final int PARSE_STATE_WIDTH = 2;
/** parsing precision */
private static final int PARSE_STATE_PRECISION = 3;
/** parsing size */
private static final int PARSE_STATE_SIZE = 4;
/** parsing type */
private static final int PARSE_STATE_TYPE = 5;
/** parsing finished */
private static final int PARSE_STATE_END = 6;
/** incomplete pattern at end of format string, throw error */
private static final int PARSE_STATE_ABORT = 7;
/** end of format string during plain text */
private static final int PARSE_STATE_TERM = 8;
/**
* This method implements the famous and ubiquituous sprintf
* formatter in Java. The arguments to the method are the formatting string
* and the list of arguments defined by the formatting instructions
* contained in the formatting string.
*
* Each element in the argument array is either a Number
object
* or any other Object
type. Whenever the formatting string
* stipulates the corresponding argument to be numeric, it is assumed this
* array element to be a Number
. If this is not the case an
* IllegalArgumentException
is thrown. If a String
* argument is stipulated by the formatting string, a simple call to the
* toString()
method of the object yields the
* String
required.
*
* SPECIFICATION
*
* sprintf
accepts a series of arguments, applies to each a
* format specifier from format
, and stores the formatted data
* to the result Strint
. The method throws an
* IllegalArgumentException
if either the format
* is incorrect, there are not enough arguments for the format
* or if any of the argument's type is wrong. sprintf
returns
* when it reaches the end of the format string. If there are more
* arguments than the format requires, excess arguments are ignored.
*
* format
is a String
containing two types of
* objects: ordinary characters (other than %
), which
* are copied unchanged to the output, and conversion specifications,
* each of which is introduced by %
. (To include
* %
in the output, use %%
in the format
* string.) A conversion specification has the following form:
*
*
* [ flags ] [ width ] [ "." prec ] [ size ]
* type
*
*
*
* The fields of the conversion specification have the following meanings:
*
*
* - flags
*
- an optional sequence of characters which control output
* justification, numeric signs, decimal points, trailing zeroes, and
* octal and hex prefixes. The flag characters are minus (
-
),
* plus (+
), space ("
"), zero (0
),
* and sharp (#
). They can appear in any combination.
*
*
*
* -
* The result of the conversion is left justified, and the right
* is padded with blanks. If you do not use this flag, the result
* is right justified, and padded on the left.
*
*
* +
* The result of a signed conversion (as determined by type)
* will always begin with a plus or minus sign. (If you do not use
* this flag, positive values do not begin with a plus sign.)
*
*
* "
" (space)
* If the first character of a signed conversion specification is
* not a sign, or if a signed conversion results in no characters,
* the result will begin with a space. If the space
* (
) flag and the plus (+
) flag both
* appear, the space flag is ignored.
*
*
* 0
* If the type is d
, i
,
* o
, u
, x
, X
,
* e
, E
, f
, g
,
* or G
: leading zeroes, are used to pad the field
* width (following any indication of sign or base); no spaces
* are used for padding. If the zero (0
) and minus
* (-
) flags both appear, the zero (0
)
* flag will be ignored. For d
, i
,
* o
, u
, x
, X
* conversions, if a precision prec is specified, the zero
* (0
) flag is ignored.
*
* Note that 0 is interpreted as a flag, not as the
* beginning of a field width.
*
*
* #
* The result is to be converted to an alternative form, according
* to the type character:
*
* o
* - Increases precision to force the first digit of the result
* to be a zero.
*
*
x
* A non-zero result will have a 0x
prefix.
*
* X
* A non-zero result will have a 0X
prefix.
*
* e
, E
, or f
* The result will always contain a decimal point even if no
* digits follow the point. (Normally, a decimal point appears
* only if a digit follows it.) Trailing zeroes are removed.
*
* g
or G
* Same as e
or E
, but trailing
* zeroes arenot removed.
*
* - all others
*
- Undefined.
*
*
*
*
* - width
*
- width is an optional minimum field width. You can either
* specify it directly as a decimal integer, or indirectly by using instead
* an asterisk (
*
), in which case an integral numeric argument
* is used as the field width. Negative field widths are not supported; if
* you attempt to specify a negative field width, it is interpreted as a
* minus (i
) flag followed by a positive field width.
*
* - prec
*
- an optional field; if present, it is introduced with
* `
.
' (a period). This field gives the maximum number of
* characters to print in a conversion; the minimum number of digits of an
* integer to print, for conversions with type d
,
* i
, o
, u
, x
, and
* X
; the maximum number of significant digits, for the
* g
and G
conversions; or the number of digits
* to print after the decimal point, for e
, E
,
* and f
conversions. You can specify the precision either
* directly as a decimal integer or indirectly by using an asterisk
* (*
), in which case an integral numeric argument is used as
* the precision. Supplying a negative precision is equivalent to
* omitting the precision. If only a period is specified the precision
* is zero. If a precision appears with any other conversion type
* than those listed here, the behavior is undefined.
*
* - size
*
h
, l
, and L
are optional size
* characters which override the default way that sprintf
* interprets the data type of the corresponding argument.
* h
forces the following d
, i
,
* o
, u
, x
, or X
* conversion type to apply to a short
or unsigned
* short
. Similarily, an l
forces the following
* d
, i
, o
, u
,
* x
, or X
conversion type to apply to
* a long
or unsigned long
. If an h
* or an l
appears with another conversion specifier, the
* behavior is undefined. L
forces a following
* e
, E
, f
, g
, or
* G
conversion type to apply to a long
* double
argument. If L
appears with any other
* conversion type, the behavior is undefined.
*
* - type
*
- type specifies what kind of conversion
sprintf
* performs. Here is a table of these:
*
*
* %
* - prints the percent character (
%
)
*
* c
* - prints arg as single character. That is the argument is
* converted to a
String
of which only the first
* character is printed.
*
* s
* - Prints characters until precision is reached or the
*
String
ends; takes any Object
whose
* toString()
method is called to get the
* String
to print.
*
* d
* - prints a signed decimal integer; takes a
Number
(same
* as i
)
*
* d
* - prints a signed decimal integer; takes a
Number
(same
* as d
)
*
* d
* - prints a signed octal integer; takes a
Number
*
* u
* - prints a unsigned decimal integer; takes a
Number
.
* This conversion is not supported correctly by the Java
* implementation and is really the same as i
.
*
* x
* - prints an unsigned hexadecimal integer (using abcdef as
* digits beyond 9; takes a
Number
*
* X
* - prints an unsigned hexadecimal integer (using ABCDEF as
* digits beyond 9; takes a
Number
*
* f
* - prints a signed value of the form [-]9999.9999; takes a
*
Number
*
* e
* - prints a signed value of the form [-]9.9999e[+|-]999; takes
* a
Number
*
* E
* - prints the same way as
e
, but using E to
* introduce the exponent; takes a Number
*
* g
* - prints a signed value in either
f
or e
* form, based on given value and precision &emdash; trailing zeros
* and the decimal point are printed only if necessary; takes a
* Number
*
* G
* - prints the same way as
g
, but using E
* for the exponent if an exponent is needed; takes a
* Number
*
* n
* - Not supported in the Java implementation, throws an
*
IllegalArgumentException
if used.
*
* p
* - Not supported in the Java implementation, throws an
*
IllegalArgumentException
if used.
*
*
*
*
*
* IMPLEMENTATION NOTES
*
* Due to the nature of the Java programming language, neither pointer
* conversions, nor unsigned
and long double
* conversions are supported.
*
* Also the Java implementation only distinguishes between
* Number
and other Object
arguments. If a
* numeric argument is expected as per the format
* String
, the current argument must be a Number
* object that is converted to a basic type as expected. If a
* String
or char
argument is expected, any
* Object is valid, whose toString()
method is used to convert
* to a String
.
*
* @param format The format string as known from the POSIX sprintf()
* C function.
* @param args The list of arguments to accomodate the format string. This
* argument list is supposed to contain at least as much entries as
* there are formatting options in the format string. If for a
* numeric option, the entry is not a number an
* IllegalArgumentException
is thrown.
*
* @return The formatted String
. An empty String
* is only returned if the format
String
* is empty. A null
value is never returned.
*
* @throws NullPointerException if the formatting string or any of the
* argument values is null
.
* @throws IllegalArgumentException if the formatting string has wrong
* format tags, if the formatting string has an incomplete
* formatting pattern at the end of the string, if the argument
* vector has not enough values to satisfy the formatting string
* or if an argument's type does not match the requirements of the
* format string.
*
*/
public static String sprintf(String format, Object[] args) {
// Return immediately if we have no arguments ....
if (format == null) {
throw new NullPointerException("format");
}
if (format.length() == 0) {
return "";
}
// Get the format string
char[] s = format.toCharArray();
// prepare the result, initial size has no sound basis
StringBuffer res = new StringBuffer( s.length * 3 / 2 );
for (int i=0,j=0, length=format.length(); i < length; ) {
int parse_state = PARSE_STATE_NONE;
BitSet flags = new BitSet(16);
int width = 0;
int precision = -1;
char fmt = ' ';
// find a start of a formatting ...
while (parse_state == PARSE_STATE_NONE) {
if (i >= length) parse_state = PARSE_STATE_TERM;
else if (s[i] == '%') {
if (i < length - 1) {
if (s[i + 1] == '%') {
res.append('%');
i++;
} else {
parse_state = PARSE_STATE_FLAGS;
}
} else {
throw new java.lang.IllegalArgumentException(
"Incomplete format at end of format string");
}
} else {
res.append(s[i]);
}
i++;
}
// Get flags, if any
while (parse_state == PARSE_STATE_FLAGS) {
if (i >= length) parse_state = PARSE_STATE_ABORT;
else if (s[i] == ' ') flags.set(FLAG_SP);
else if (s[i] == '-') flags.set(FLAG_LJ);
else if (s[i] == '+') flags.set(FLAG_SI);
else if (s[i] == '0') flags.set(FLAG_ZE);
else if (s[i] == '#') flags.set(FLAG_AL);
else {
parse_state = PARSE_STATE_WIDTH;
i--;
}
i++;
}
// Get width specification
while (parse_state == PARSE_STATE_WIDTH) {
if (i >= length) {
parse_state = PARSE_STATE_ABORT;
} else if ('0' <= s[i] && s[i] <= '9') {
width = width * 10 + s[i] - '0';
i++;
} else {
// finished with digits or none at all
// if width is a '*' take width from arg
if (s[i] == '*') {
// Check whether we have an argument
if (j >= args.length) {
throw new IllegalArgumentException("Missing " +
"argument for the width");
}
try {
width = ((Number)(args[j++])).intValue();
} catch (ClassCastException cce) {
// something wrong with the arg
throw new IllegalArgumentException("Width " +
"argument is not numeric");
}
i++;
}
// if next is a dot, then we have a precision, else a size
if (s[i] == '.') {
parse_state = PARSE_STATE_PRECISION;
precision = 0;
i++;
} else {
parse_state = PARSE_STATE_SIZE;
}
}
}
// Get precision
while (parse_state == PARSE_STATE_PRECISION) {
if (i >= length) {
parse_state = PARSE_STATE_ABORT;
} else if ('0' <= s[i] && s[i] <= '9') {
precision = precision * 10 + s[i] - '0';
i++;
} else {
// finished with digits or none at all
// if width is a '*' take precision from arg
if (s[i] == '*') {
// Check whether we have an argument
if (j >= args.length) {
throw new IllegalArgumentException("Missing " +
"argument for the precision");
}
try {
width = ((Number)(args[j++])).intValue();
} catch (ClassCastException cce) {
// something wrong with the arg
throw new IllegalArgumentException("Precision " +
"argument is not numeric");
}
i++;
}
parse_state = PARSE_STATE_SIZE;
}
}
// Get size character
if (parse_state == PARSE_STATE_SIZE) {
if (i >= length) parse_state = 6;
else {
if (s[i] == 'h') {
flags.set(FLAG_SHORT);
i++;
} else if (s[i] == 'l' || s[i] == 'L') {
flags.set(FLAG_LONG);
i++;
}
parse_state = PARSE_STATE_TYPE;
}
}
// Get format character
if (parse_state == PARSE_STATE_TYPE) {
if (i >= length) parse_state = PARSE_STATE_ABORT;
else {
fmt = s[i];
i++;
parse_state = PARSE_STATE_END;
}
}
// Now that we have anything, format it ....
if (parse_state == PARSE_STATE_END) {
// Check whether we have an argument
if (j >= args.length) {
throw new IllegalArgumentException("Not enough parameters for the format string");
}
try {
// Convert the argument according to the flag
switch (fmt) {
case 'd': // decimal - fall through
case 'i': // integral - fall through
case 'x': // hexadecimal, lower case - fall through
case 'X': // hexadecimal, upper case - fall through
case 'o': // octal - fall through
case 'u': // unsigned (not really supported)
format(res, (Number)args[j], fmt, width, precision,
flags);
break;
case 'f': // float - fall through
case 'e': // exponential, lower case - fall through
case 'E': // exponential, upper case - fall through
case 'g': // float or exp., lower case - fall through
case 'G': // float or exp., upper case - fall through
format(res, ((Number)args[j]).doubleValue(), fmt,
width, precision, flags);
break;
case 'c': // character
precision = 1;
// fall through
case 's': // string
String val = args[j].toString();
if (val.length() > precision && precision > 0) {
val = val.substring(0, precision);
}
flags.clear(FLAG_ZE);
format(res, val, "", width, flags);
break;
default : // unknown format
throw new IllegalArgumentException("Unknown " +
"conversion type " + fmt);
}
} catch (ClassCastException cce) {
// something wrong with the arg
throw new IllegalArgumentException("sprintf: Argument #" +
j + " of type " + args[j].getClass().getName() +
" does not match format " + fmt);
}
// goto the next argument
j++;
}
// if the format string is not complete
if (parse_state == PARSE_STATE_ABORT) {
throw new java.lang.IllegalArgumentException(
"Incomplete format at end of format string");
}
} // while i
return res.toString();
}
/**
* Formats a string according to format and argument. This method only
* supports string formats. See {@link #sprintf(String, Object[])} for details.
*
* @param format The format string
* @param a0 The single parameter
*
* @return the result from sprintf(format, new Object[]{ a0 })
.
*
* @throws IllegalArgumentException from {@link #sprintf(String, Object[])}.
*/
public static String sprintf(String format, Object a0) {
return sprintf(format, new Object[]{a0});
}
/**
* Formats a string according to format and argument. This method only
* supports string formats. See {@link #sprintf(String, Object[])} for details.
*
* @param format The format string
* @param a0 The first parameter
* @param a1 The second parameter
*
* @return the result from sprintf(format, new Object[]{ ... })
.
*
* @throws IllegalArgumentException from {@link #sprintf(String, Object[])}.
*/
public static String sprintf(String format, Object a0, Object a1) {
return sprintf(format, new Object[]{a0, a1});
}
/**
* Formats a string according to format and argument. This method only
* supports string formats. See {@link #sprintf(String, Object[])} for details.
*
* @param format The format string
* @param a0 The first parameter
* @param a1 The second parameter
* @param a2 The thrid parameter
*
* @return the result from sprintf(format, new Object[]{ ... })
.
*
* @throws IllegalArgumentException from {@link #sprintf(String, Object[])}.
*/
public static String sprintf(String format, Object a0, Object a1,
Object a2) {
return sprintf(format, new Object[]{a0, a1, a2});
}
/**
* Formats a string according to format and argument. This method only
* supports string formats. See {@link #sprintf(String, Object[])} for details.
*
* @param format The format string
* @param a0 The first parameter
* @param a1 The second parameter
* @param a2 The thrid parameter
* @param a3 The fourth parameter
*
* @return the result from sprintf(format, new Object[]{ ... })
.
*
* @throws IllegalArgumentException from {@link #sprintf(String, Object[])}.
*/
public static String sprintf(String format, Object a0, Object a1,
Object a2, Object a3) {
return sprintf(format, new Object[]{a0, a1, a2, a3});
}
/**
* Formats a string according to format and argument. This method only
* supports string formats. See {@link #sprintf(String, Object[])} for details.
*
* @param format The format string
* @param a0 The first parameter
* @param a1 The second parameter
* @param a2 The thrid parameter
* @param a3 The fourth parameter
* @param a4 The fifth parameter
*
* @return the result from sprintf(format, new Object[]{ ... })
.
*
* @throws IllegalArgumentException from {@link #sprintf(String, Object[])}.
*/
public static String sprintf(String format, Object a0, Object a1,
Object a2, Object a3, Object a4) {
return sprintf(format, new Object[]{a0, a1, a2, a3, a4});
}
/** The list of characters that valid label characters */
private final static String[] labelCharMapping = new String[] {
"_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_",
"_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_",
"_","_","_","_","_","_","_","_","_","_","_","_","_","-","_","_",
"0","1","2","3","4","5","6","7","8","9","_","_","_","_","_","_",
"_","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
"p","q","r","s","t","u","v","w","x","y","z","_","_","_","_","_",
"_","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
"p","q","r","s","t","u","v","w","x","y","z","_","_","_","_","_",
"_","f","_","_","_","fi","fi","_","_","_","_","_","_","_","_","_",
"_","_","_","_","_","_","_","_","_","_","_","_","y","_","_","_",
"_","i","c","p","o","v","_","s","_","_","_","_","_","_","_","_",
"_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_",
"a","a","a","a","ae","a","ae","c","e","e","e","e","i","i","i","i",
"d","n","o","o","o","o","oe","x","o","u","u","u","ue","y","b","ss",
"a","a","a","a","ae","a","ae","c","e","e","e","e","i","i","i","i",
"o","n","o","o","o","o","oe","_","o","u","u","u","ue","y","b","y"
};
/**
* Non-valid handle characters: / \ : * ? " < > | and carriage return.
* (That's slash, backslash, colon, asterisk, question mark, quotation mark,
* less than symbol, greater than symbol, pipe, and carriage return.)
*/
public final static String NON_VALID_CHARS = "/\\:*?\"<>|\n";
/**
* Create a valid label out of an arbitrary string.
* @param labelhint the given label string that we will examine
* and convert any illegal character if necessary
* @return a valid label string
*/
public static String createValidLabel(String labelhint) {
char[] chrs=labelhint.toCharArray();
StringBuffer labelbase = new StringBuffer(chrs.length);
for (int idx=0; idx=0 && c16) {
break;
}
labelbase.append(repl);
}
return labelbase.toString();
}
/**
* Checks if the label is not empty and contains all valid label chars.
* @param labelhint the labelhint to check
* @throws IllegalArgumentException if the label does not contain all valid
* chars.
*/
public static void validateLabel(String labelhint) {
if (labelhint==null || labelhint.equals("")) {
throw new IllegalArgumentException("Exact Label must not be empty.");
}
// bug 8262 - do not allow '.' and '..'
if (".".equals(labelhint) || "..".equals(labelhint)) {
throw new IllegalArgumentException("Exact Label must not be ''.'' or ''..''");
}
for (int idx=0; idx=0 ||
labelhint.charAt(idx) > 127) {
throw new IllegalArgumentException("Exact Label contains illegal character: \'" + labelhint.charAt(idx) + "\'");
}
}
}
//---------- internal ------------------------------------------------------
/**
* Convert a Date formatting string in POSIX strftime() format to the
* pattern format used by the Java SimpleDateFormat class.
*
* These are the symbols used in SimpleDateFormat to which we convert
* our strftime() symbols.
*
*
* Symbol Meaning Presentation Example
* G era designator (Text) AD
* y year (Number) 1996
* M month in year (Text & Number) July & 07
* d day in month (Number) 10
* h hour in am/pm (1~12) (Number) 12
* H hour in day (0~23) (Number) 0
* m minute in hour (Number) 30
* s second in minute (Number) 55
* S millisecond (Number) 978
* E day in week (Text) Tuesday
* D day in year (Number) 189
* F day of week in month (Number) 2 (2nd Wed in July)
* w week in year (Number) 27
* W week in month (Number) 2
* a am/pm marker (Text) PM
* k hour in day (1~24) (Number) 24
* K hour in am/pm (0~11) (Number) 0
* z time zone (Text) Pacific Standard Time
* ' escape for text (Delimiter)
* '' single quote (Literal) '
*
*
* @param posixFormat The formatting pattern in POSIX strftime() format.
*
* @return The Date formatting pattern in SimpleDateFormat pattern format.
*
* todo: Check for the more or less complete support of all pattern tags.
*/
private static String convertFormat(String posixFormat) {
char[] format = posixFormat.toCharArray();
StringBuffer jFormat = new StringBuffer(format.length);
boolean inString = false;
for (int i = 0; i < format.length; i++) {
if (format[i] == '\'') {
// insert a second tick
jFormat.append('\'');
} else if (format[i] == '%') {
if (inString) {
jFormat.append('\'');
inString = false;
}
switch (format[++i]) {
case'%':
// just a single '%'
jFormat.append('%');
break;
case 'a':
// locale's abbreviated weekday name
jFormat.append("EEE");
break;
case 'A':
// locale's full weekday name
jFormat.append("EEEE");
break;
case 'b':
// locale's abbreviated month name
jFormat.append("MMM");
break;
case 'B':
// locale's full month name
jFormat.append("MMMM");
break;
case 'c':
// locale's appropriate date and time representation
break;
case 'C':
// century number as a decimal number (00-99)
// Not supported
break;
case 'd':
// day of month (01-31)
jFormat.append("dd");
break;
case 'D':
// date as %m/%d/%y
jFormat.append("MM/dd/yy");
break;
case 'e':
// day of month ( 1-31)
jFormat.append("d");
break;
case 'h':
// locale's abbreviated month name
jFormat.append("MMM");
break;
case 'H':
// hour (00-23)
jFormat.append("HH");
break;
case 'I':
// hour (01-12)
jFormat.append("hh");
break;
case 'j':
// day number of year (001-366)
jFormat.append("DDD");
break;
case 'K':
// plus C !!!
if (format[++i] == 'C') {
// locale's appropriate date and time representation
}
break;
case 'm':
// month number (01-12)
jFormat.append("MM");
break;
case 'M':
// minute (00-59)
jFormat.append("mm");
break;
case 'n':
// new line
jFormat.append( System.getProperty("line.separator", "\n") );
break;
case 'p':
// locale's equivalent of either AM and PM
jFormat.append("aa");
break;
case 'r':
// locale's 12-hour time representation, default %I:%M:%S [AM|PM]
jFormat.append("hh:mm:ss aa");
break;
case 'R':
// time as %H:%M
jFormat.append("hh:mm");
break;
case 'S':
// seconds (00-61) [ leap seconds ;-) ]
jFormat.append("ss");
break;
case 't':
// tab character
jFormat.append( '\t' );
break;
case 'T':
// time as %H:%M:%S
jFormat.append("HH:mm:ss");
break;
case 'U':
// week number of year (00-53), sunday is first day of week
jFormat.append("ww");
break;
case 'w':
// weekday number (0-6, 0=sunday)
jFormat.append("E");
break;
case 'W':
// week number of year (00-53), monday is first day of week
jFormat.append("ww");
break;
case 'x':
// locale's appropriate date representation
break;
case 'X':
// locale's appropriate time representation
break;
case 'y':
// year within century (00-99)
jFormat.append("yy");
break;
case 'Y':
// year as %c%y (e.g. 1986)
jFormat.append("yyyy");
break;
case 'Z':
// time zone name or no characters if no time zone exists
jFormat.append("zzz");
break;
default:
// ignore
}
} else if (Character.isLetterOrDigit(format[i])) {
if (!inString) {
inString = true;
jFormat.append('\'');
}
jFormat.append(format[i]);
} else {
jFormat.append(format[i]);
}
}
if (inString) {
jFormat.append('\'');
}
return jFormat.toString();
}
/**
* Implements the integral number formatting part of the
* sprintf()
method, that is, this method handles d, i, o, u,
* x, and X formatting characters.
*
* @param buf The formatted number is appended to this string buffer
* @param num The number object to format
* @param fmt The format character defining the radix of the number
* @param width The minimum field width for the number
* @param precision The minimum number of digits to print for the number,
* this does not include any signs or prefix characters
* @param flags The flags governing the formatting. This is a combination
* of the FLAG_* constants above.
*
* @return The formatted string
*
* @see sprintf()
*/
private static StringBuffer format(StringBuffer buf, Number num,
char fmt, int width, int precision, BitSet flags) {
String numStr;
String prefStr = "";
boolean toUpper = (fmt == 'X');
// Check precision and make default
if (precision >= 0) {
flags.clear(FLAG_ZE);
} else {
precision = 1;
}
// Get the value and adjust size interpretation
long val;
long sizeMask;
if (flags.get(FLAG_SHORT)) {
val = num.shortValue();
sizeMask = 0xffffL;
} else if (flags.get(FLAG_LONG)) {
val = num.longValue();
sizeMask = 0xffffffffffffffffL;
} else {
val = num.intValue();
sizeMask = 0xffffffffL;
}
// check formatting type
if (fmt == 'x' || fmt == 'X') {
numStr = Long.toHexString(val & sizeMask);
if (toUpper) {
numStr = numStr.toUpperCase();
}
if (flags.get(FLAG_AL)) {
prefStr = toUpper ? "0X" : "0x";
}
} else if (fmt == 'o') {
numStr = Long.toOctalString(val & sizeMask);
if (flags.get(FLAG_AL) && val != 0 && precision <= numStr.length()) {
precision = numStr.length() + 1;
}
} else {
numStr = Long.toString(val);
// move sign to prefix if negative, or set '+'
if (val < 0) {
prefStr = "-";
numStr = numStr.substring(1);
} else if (flags.get(FLAG_SI)) {
prefStr = "+";
}
}
// prefix 0 for precision
if (precision > numStr.length()) {
StringBuffer tmp = new StringBuffer(precision);
for (precision -= numStr.length(); precision > 0; precision--) {
tmp.append('0');
}
numStr = tmp.append(numStr).toString();
}
return format(buf, numStr, prefStr, width, flags);
}
/**
* Implements the floating point number formatting part of the
* sprintf()
method, that is, this method handles f, e, E, g,
* and G formatting characters.
*
* @param buf The formatted number is appended to this string buffer
* @param num The numeric value to format
* @param fmt The format character defining the floating point format
* @param width The minimum field width for the number
* @param precision Depending on fmt
either the number of
* digits after the decimal point or the number of significant
* digits.
* @param flags The flags governing the formatting. This is a combination
* of the FLAG_* constants above.
*
* @return The formatted string
*
* @see sprintf()
*/
private static StringBuffer format(StringBuffer buf, double num,
char fmt, int width, int precision, BitSet flags) {
BigDecimal val = new BigDecimal(num).abs();
// the exponent character, will be defined if exponent is needed
char expChar = 0;
// the exponent value
int exp;
if (fmt != 'f') {
exp = val.unscaledValue().toString().length() - val.scale() - 1;
} else {
exp = 0;
}
// force display of the decimal dot, if otherwise omitted
boolean needDot = (precision == 0 && flags.get(FLAG_AL));
// for fmt==g|G : treat trailing 0 and decimal dot specially
boolean checkTrails = false;
// get a sensible precision value
if (precision < 0) {
precision = 6;
}
switch (fmt) {
case 'G': // fall through
case 'g':
// decrement precision, to simulate significance
if (precision > 0) {
precision--;
}
// we have to check trailing zeroes later
checkTrails = true;
// exponent does not stipulate exp notation, break here
if (exp <= precision) {
precision -= exp;
break;
}
// fall through for exponent handling
case 'E': // fall through
case 'e':
// place the dot after the first decimal place
val = val.movePointLeft(exp);
// define the exponent character
expChar = (fmt == 'e' || fmt == 'g') ? 'e' : 'E';
break;
}
// only rescale if the precision is positive, may be negative
// for g|G
if (precision >= 0) {
val = val.setScale(precision, BigDecimal.ROUND_HALF_UP);
}
// convert the number to a string
String numStr = val.toString();
// for g|G : check trailing zeroes
if (checkTrails) {
if (flags.get(FLAG_AL)) {
// need a dot, if not existing for alternative format
needDot |= (numStr.indexOf('.') < 0);
} else {
// remove trailing dots and zeros
int dot = numStr.indexOf('.');
if (dot >= 0) {
int i = numStr.length()-1;
while (i>=dot && numStr.charAt(i)=='0') {
i--;
}
// if stopped at dot, remove it
if (i > dot) {
i++;
}
numStr = numStr.substring(0, i);
}
}
}
// Get a buffer with the number up to now
StringBuffer numBuf = new StringBuffer(numStr);
// if we need a decimal dot, add it
if (needDot) {
numBuf.append('.');
}
// we have an exponent to add
if (expChar != 0) {
numBuf.append(expChar);
numBuf.append(exp < 0 ? '-' : '+');
if (exp < 10) {
numBuf.append('0');
}
numBuf.append(exp);
}
// define the number's sign as the prefix for later formatting
String prefStr;
if (num < 0) {
prefStr = "-";
} else if (flags.get(FLAG_SI)) {
prefStr = "+";
} else {
prefStr = "";
}
// now format it and up we go
return format(buf, numBuf.toString(), prefStr, width, flags);
}
/**
* Formats the String
appending to the
* StringBuffer
at least the String
and
* justifying according to the flags.
*
* The flags will be interpreted as follows :
* * if {@link #FLAG_LJ} is set, append blanks for left justification
* * else if {@link #FLAG_ZE} is set, insert zeroes between prefStr and str
* * else prepend blanks for right justification
*
* @param buf The StringBuffer
to append the formatted result
* to.
* @param str The String
to be appended with surrounding
* blanks, zeroes and prefStr
depending on the
* flags
. This is usually the real string to print
* like "ape" or "4.5E99".
* @param prefStr An optional prefix String
to be appended
* in front of the String
. This is usually the prefix
* string for numeric str
values, for example
* "-", "0x", or "+". The reason for this separation is that the
* {@link #FLAG_ZE} flag will insert zeroes between the prefix and
* the string itself to fill the field to the width.
* @param width The minimal field width. If the field width is larger than
* the sum of the lengths of str
and
* prefStr
, blanks or zeroes will be prepended or
* appended according to the flags value.
* @param flags The flags indicating where blanks or zeroes will be filled.
* See above for the interpretation of flags.
*
* @throws NullPointerException if any of buf
,
* str
, prefStr
or flags
* is null
.
*/
private static StringBuffer format(StringBuffer buf, String str,
String prefStr, int width, BitSet flags) {
int numFill = width - prefStr.length() - str.length();
int preZero = 0;
int preBlank = 0;
int postBlank = 0;
if (flags.get(FLAG_LJ)) {
postBlank = numFill;
} else if (flags.get(FLAG_ZE)) {
preZero = numFill;
} else {
preBlank = numFill;
}
for ( ; preBlank > 0; preBlank--) buf.append(' ');
buf.append(prefStr);
for ( ; preZero > 0; preZero--) buf.append('0');
buf.append(str);
for ( ; postBlank > 0; postBlank--) buf.append(' ');
return buf;
}
/**
* Join two paths or handles, fixing the slashes.
* All these will go to "/a/b":
* /a/ + /b/ (trailing slashes are removed)
* /a + /b
* /a/ + b
* /a/ + b/
* /a// + /////b (i am paranoid)
*
* Note that leading slashes are untouched, so variations
* on the above without the leading slash on 'a' would go
* to "a/b".
* @deprecated use {@link #makeCanonicalPath(String)}
*/
public static String joinFixSlash(String first, String second) {
StringBuffer result = new StringBuffer((first.startsWith("/"))?"/":"");
String[] firstSplit = explode(first, '/');
String[] secondSplit = explode(second, '/');
for (int i=0; i 1) {
result.setLength(result.length() - 1);
}
return result.toString();
}
/**
* Performs variable replacement on the given string value.
* Each ${...}
sequence within the given value is replaced
* with the value of the named parser variable. If a variable is not found
* in the properties an IllegalArgumentException is thrown unless
* ignoreMissing
is true
. In the later case, the
* missing variable is replaced by the empty string.
*
* @param value the original value
* @param ignoreMissing if true
, missing variables are replaced
* by the empty string.
* @return value after variable replacements
* @throws IllegalArgumentException if the replacement of a referenced
* variable is not found
*/
public static String replaceVariables(Properties variables, String value,
boolean ignoreMissing)
throws IllegalArgumentException {
StringBuffer result = new StringBuffer();
// Value:
// +--+-+--------+-+-----------------+
// | |p|--> |q|--> |
// +--+-+--------+-+-----------------+
int p = 0, q = value.indexOf("${"); // Find first ${
while (q != -1) {
result.append(value.substring(p, q)); // Text before ${
p = q;
q = value.indexOf("}", q + 2); // Find }
if (q != -1) {
String variable = value.substring(p + 2, q);
String replacement = variables.getProperty(variable);
if (replacement == null) {
if (ignoreMissing) {
replacement = "";
} else {
throw new IllegalArgumentException(
"Replacement not found for ${" + variable + "}.");
}
}
result.append(replacement);
p = q + 1;
q = value.indexOf("${", p); // Find next ${
}
}
result.append(value.substring(p, value.length())); // Trailing text
return result.toString();
}
/**
* Parses an ISO8601-compliant date/time string.
* (see ISO 8601).
*
* The currently supported format is:
*
* ±YYYY-MM-DDThh:mm:ss.SSSTZD
*
* where:
*
* ±YYYY = four-digit year with optional sign where values <= 0 are
* denoting years BCE and values > 0 are denoting years CE,
* e.g. -0001 denotes the year 2 BCE, 0000 denotes the year 1 BCE,
* 0001 denotes the year 1 CE, and so on...
* MM = two-digit month (01=January, etc.)
* DD = two-digit day of month (01 through 31)
* hh = two digits of hour (00 through 23) (am/pm NOT allowed)
* mm = two digits of minute (00 through 59)
* ss = two digits of second (00 through 59)
* SSS = three digits of milliseconds (000 through 999)
* TZD = time zone designator, Z for Zulu (i.e. UTC) or an offset from UTC
* in the form of +hh:mm or -hh:mm
*
*
* @param text the date/time string to be parsed
* @return a Calendar
, or null
if the input could
* not be parsed
* @throws IllegalArgumentException if a null
argument is passed
*/
public static Calendar parseISO8601(String text) {
if (text == null) {
throw new IllegalArgumentException("argument can not be null");
}
// check optional leading sign
char sign;
int start;
if (text.startsWith("-")) {
sign = '-';
start = 1;
} else if (text.startsWith("+")) {
sign = '+';
start = 1;
} else {
sign = '+'; // no sign specified, implied '+'
start = 0;
}
/**
* the expected format of the remainder of the string is:
* YYYY-MM-DDThh:mm:ss.SSSTZD
*
* note that we cannot use java.text.SimpleDateFormat for
* parsing because it can't handle years <= 0 and TZD's
*/
int year, month, day, hour, min, sec, ms;
String tzID;
try {
// year (YYYY)
year = Integer.parseInt(text.substring(start, start + 4));
start += 4;
// delimiter '-'
if (text.charAt(start) != '-') {
return null;
}
start++;
// month (MM)
month = Integer.parseInt(text.substring(start, start + 2));
start += 2;
// delimiter '-'
if (text.charAt(start) != '-') {
return null;
}
start++;
// day (DD)
day = Integer.parseInt(text.substring(start, start + 2));
start += 2;
// delimiter 'T'
if (text.charAt(start) != 'T') {
return null;
}
start++;
// hour (hh)
hour = Integer.parseInt(text.substring(start, start + 2));
start += 2;
// delimiter ':'
if (text.charAt(start) != ':') {
return null;
}
start++;
// minute (mm)
min = Integer.parseInt(text.substring(start, start + 2));
start += 2;
// delimiter ':'
if (text.charAt(start) != ':') {
return null;
}
start++;
// second (ss)
sec = Integer.parseInt(text.substring(start, start + 2));
start += 2;
// delimiter '.'
if (text.charAt(start) != '.') {
return null;
}
start++;
// millisecond (SSS)
ms = Integer.parseInt(text.substring(start, start + 3));
start += 3;
// time zone designator (Z or +00:00 or -00:00)
if (text.charAt(start) == '+' || text.charAt(start) == '-') {
// offset to UTC specified in the format +00:00/-00:00
tzID = "GMT" + text.substring(start);
} else if (text.substring(start).equals("Z")) {
tzID = "GMT";
} else {
// invalid time zone designator
return null;
}
} catch (IndexOutOfBoundsException e) {
return null;
} catch (NumberFormatException e) {
return null;
}
TimeZone tz = TimeZone.getTimeZone(tzID);
// verify id of returned time zone (getTimeZone defaults to "GMT")
if (!tz.getID().equals(tzID)) {
// invalid time zone
return null;
}
// initialize Calendar object
Calendar cal = Calendar.getInstance(tz);
cal.setLenient(false);
// year and era
if (sign == '-' || year == 0) {
// not CE, need to set era (BCE) and adjust year
cal.set(Calendar.YEAR, year + 1);
cal.set(Calendar.ERA, GregorianCalendar.BC);
} else {
cal.set(Calendar.YEAR, year);
cal.set(Calendar.ERA, GregorianCalendar.AD);
}
// month (0-based!)
cal.set(Calendar.MONTH, month - 1);
// day of month
cal.set(Calendar.DAY_OF_MONTH, day);
// hour
cal.set(Calendar.HOUR_OF_DAY, hour);
// minute
cal.set(Calendar.MINUTE, min);
// second
cal.set(Calendar.SECOND, sec);
// millisecond
cal.set(Calendar.MILLISECOND, ms);
try {
/**
* the following call will trigger an IllegalArgumentException
* if any of the set values are illegal or out of range
*/
cal.getTime();
} catch (IllegalArgumentException e) {
return null;
}
return cal;
}
/**
* Formats a Calendar
value into an ISO8601-compliant
* date/time string.
*
* @param cal the time value to be formatted into a date/time string.
* @return the formatted date/time string.
* @throws IllegalArgumentException if a null
argument is passed
*/
public static String formatISO8601(Calendar cal) {
if (cal == null) {
throw new IllegalArgumentException("argument can not be null");
}
// determine era and adjust year if necessary
int year = cal.get(Calendar.YEAR);
if (cal.isSet(Calendar.ERA)
&& cal.get(Calendar.ERA) == GregorianCalendar.BC) {
/**
* calculate year using astronomical system:
* year n BCE => astronomical year -n + 1
*/
year = 0 - year + 1;
}
/**
* the format of the date/time string is:
* YYYY-MM-DDThh:mm:ss.SSSTZD
*
* note that we cannot use java.text.SimpleDateFormat for
* formatting because it can't handle years <= 0 and TZD's
*/
StringBuffer buf = new StringBuffer();
// year ([-]YYYY)
pad0(buf, 4, year);
buf.append('-');
// month (MM)
pad0(buf, 2, cal.get(Calendar.MONTH) + 1);
buf.append('-');
// day (DD)
pad0(buf, 2, cal.get(Calendar.DAY_OF_MONTH));
buf.append('T');
// hour (hh)
pad0(buf, 2, cal.get(Calendar.HOUR_OF_DAY));
buf.append(':');
// minute (mm)
pad0(buf, 2, cal.get(Calendar.MINUTE));
buf.append(':');
// second (ss)
pad0(buf, 2, cal.get(Calendar.SECOND));
buf.append('.');
// millisecond (SSS)
pad0(buf, 3, cal.get(Calendar.MILLISECOND));
// time zone designator (Z or +00:00 or -00:00)
TimeZone tz = cal.getTimeZone();
// determine offset of timezone from UTC (incl. daylight saving)
int offset = tz.getOffset(cal.getTimeInMillis());
if (offset != 0) {
int hours = Math.abs((offset / (60 * 1000)) / 60);
int minutes = Math.abs((offset / (60 * 1000)) % 60);
buf.append(offset < 0 ? '-' : '+');
pad0(buf, 2, hours);
buf.append(':');
pad0(buf, 2, minutes);
} else {
buf.append('Z');
}
return buf.toString();
}
private static void pad0(StringBuffer buf, int len, int nr) {
String val = String.valueOf(nr);
while (len-- > val.length()) {
buf.append('0');
}
buf.append(val);
}
}