package er.extensions.foundation;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.MalformedInputException;
import java.text.Format;
import java.text.ParseException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.appserver.WOApplication;
import com.webobjects.eoaccess.EOAdaptorOperation;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EODatabaseOperation;
import com.webobjects.eoaccess.EOUtilities;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSPropertyListSerialization;
import com.webobjects.foundation.NSSelector;
import com.webobjects.foundation.NSTimestamp;
import er.extensions.appserver.ERXMessageEncoding;
import er.extensions.eof.ERXConstant;
import er.extensions.formatters.ERXSimpleHTMLFormatter;
/**
* Collection of {@link java.lang.String String} utilities. Contains
* the base localization support.
*/
public class ERXStringUtilities {
/** Holds the default display language, which is English */
private static final String DEFAULT_TARGET_DISPLAY_LANGUAGE = "English";
/** Holds the chars for hex encoding */
public static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/** Holds the distance key */
private static final String _DISTANCE = "distance";
/** Holds the ascending <code>EOSortOrdering</code>s */
public static final NSArray SORT_ASCENDING =
new NSArray(new Object [] { new EOSortOrdering(_DISTANCE, EOSortOrdering.CompareAscending) });
/** Holds the ascending <code>EOSortOrdering</code>s */
public static final NSArray SORT_DESCENDING =
new NSArray(new Object [] { new EOSortOrdering(_DISTANCE, EOSortOrdering.CompareDescending) });
/** Holds characters that have special meaning for regex */
public static final String SpecialRegexCharacters = ".*[]{}()?\\+%$!^";
/**
* Holds the array of default display languages. Holds
* a single entry for English.
*/
private static NSArray _defaultTargetDisplayLanguages = new NSArray(DEFAULT_TARGET_DISPLAY_LANGUAGE);
/** holds the base adjustment for fuzzy matching */
// FIXME: Not thread safe
// MOVEME: Needs to go with the fuzzy matching stuff
protected static double adjustement = 0.5;
private static final Logger log = LoggerFactory.getLogger(ERXStringUtilities.class);
/**
* Sets the base adjustment used for fuzzy matching
* @param newAdjustement factor to be used.
*/
// FIXME: Not thread safe.
// MOVEME: fuzzy matching stuff
public static void setAdjustement(double newAdjustement) {
adjustement = newAdjustement;
}
/**
* Fuzzy matching is useful for catching user entered typos. For example
* if a user is searching for a company named 'Aple' within your application
* they aren't going to find it. Thus the idea of fuzzy matching, meaning you
* can define a threshold of 'how close can they be' type of thing.
*
* @param name to be matched against
* @param entityName name of the entity to perform the match against.
* @param propertyKey to be matched against
* @param synonymsKey allows objects to have additional values to be matched
* against in addition to just the value of the propertyKey
* @param ec context to fetch data in
* @param cleaner object used to clean a string, for example the cleaner might
* strip out the words 'The' and 'Inc.'
* @param sortOrderings can be either <code>SORT_ASCENDING</code> or <code>SORT_DESCENDING</code>
* to tell how the results should be sorted.
* @return an array of objects that match in a fuzzy manner the name passed in.
*/
// FIXME: Should move all of fuzzy matching stuff in ERXFuzzyMatchCleaner and then add a static inner inerface
// IMPROVEME: too many parameters.
public static NSArray fuzzyMatch(String name,
String entityName,
String propertyKey,
String synonymsKey,
EOEditingContext ec,
ERXFuzzyMatchCleaner cleaner,
NSArray sortOrderings ){
String eoKey = "eo";
NSMutableArray<NSMutableDictionary<String, Object>> results = new NSMutableArray<NSMutableDictionary<String, Object>>();
EOFetchSpecification fs = new EOFetchSpecification( entityName, null, null );
fs.setFetchesRawRows( true );
NSArray<String> pks = EOUtilities.entityNamed( ec, entityName ).primaryKeyAttributeNames();
NSMutableArray<String> keyPaths = new NSMutableArray<>(pks);
keyPaths.addObject( propertyKey );
if( synonymsKey != null )
keyPaths.addObject( synonymsKey );
//we use only the strictly necessary keys.
fs.setRawRowKeyPaths( keyPaths );
NSArray<NSDictionary<String, Object>> rawRows = ec.objectsWithFetchSpecification( fs );
if(name == null)
name = "";
name = name.toUpperCase();
String cleanedName = cleaner.cleanStringForFuzzyMatching(name);
for(Enumeration e = rawRows.objectEnumerator(); e.hasMoreElements(); ){
NSMutableDictionary<String, Object> dico = ((NSDictionary)e.nextElement()).mutableClone();
Object value = dico.valueForKey(propertyKey);
boolean trySynonyms = true;
//First try to match with the name of the eo
if( value!=null && value instanceof String){
String comparedString = ((String)value).toUpperCase();
String cleanedComparedString = cleaner.cleanStringForFuzzyMatching(comparedString);
if( (StringUtils.getLevenshteinDistance(name, comparedString) <=
Math.min((double)name.length(), (double)comparedString.length())*adjustement ) ||
(StringUtils.getLevenshteinDistance(cleanedName, cleanedComparedString) <=
Math.min((double)cleanedName.length(), (double)cleanedComparedString.length())*adjustement)){
dico.setObjectForKey( Double.valueOf(StringUtils.getLevenshteinDistance(name, comparedString)), _DISTANCE );
NSDictionary<String, Object> pkValues = new NSDictionary<>(dico.objectsForKeys(pks, NSKeyValueCoding.NullValue ), pks);
dico.setObjectForKey( EOUtilities.faultWithPrimaryKey( ec, entityName, pkValues ), eoKey );
results.addObject( dico );
trySynonyms = false;
}
}
//Then try to match using the synonyms vector
if(trySynonyms && synonymsKey != null){
Object synonymsString = dico.valueForKey(synonymsKey);
if(synonymsString != null && synonymsString instanceof String){
Object plist = NSPropertyListSerialization.propertyListFromString((String)synonymsString);
Vector v = (Vector)plist;
for(int i = 0; i< v.size(); i++){
String comparedString = ((String)v.elementAt(i)).toUpperCase();
if((StringUtils.getLevenshteinDistance(name, comparedString) <=
Math.min((double)name.length(), (double)comparedString.length())*adjustement) ||
(StringUtils.getLevenshteinDistance(cleanedName, comparedString) <=
Math.min((double)cleanedName.length(), (double)comparedString.length())*adjustement)){
dico.setObjectForKey( Double.valueOf(StringUtils.getLevenshteinDistance(name, comparedString)), _DISTANCE );
NSDictionary<String, Object> pkValues = new NSDictionary<>(dico.objectsForKeys(pks, NSKeyValueCoding.NullValue ), pks);
dico.setObjectForKey( EOUtilities.faultWithPrimaryKey( ec, entityName, pkValues ), eoKey );
results.addObject( dico );
break;
}
}
}
}
}
if( sortOrderings != null ) {
results = (NSMutableArray<NSMutableDictionary<String, Object>>) EOSortOrdering.sortedArrayUsingKeyOrderArray(results, sortOrderings);
}
return (NSArray) results.valueForKey( eoKey );
}
/**
* Gets a localized string for a given key in the application's
* Localizable strings file for the default language (English).
* @param key to be lookup in the strings file
* @return string value of the looked up key
*/
// CHECKME: Should this be using the framework search order?
public static String localizedStringForKey(String key) {
return localizedStringForKey(key, null, null);
}
/**
* Gets a localized string for a given key in a given framework's
* Localizable strings file for the default language (English).
* @param key to be lookup in the strings file
* @param framework name, specify app or null to perform the
* lookup in the application's resources.
* @return string value of the looked up key
*/
public static String localizedStringForKey(String key, String framework) {
return localizedStringForKey(key, framework, null);
}
/**
* Gets a localized string for a given key in a given framework's
* Localizable strings file using the array of languages as the
* search order for the key.
* @param key to be lookup in the strings file
* @param framework name, specify app or null to perform the
* lookup in the application's resources.
* @param languages array to search for the key in
* @return string value of the looked up key
*/
public static String localizedStringForKey(String key, String framework, NSArray languages) {
languages = languages != null && languages.count() > 0 ? languages : _defaultTargetDisplayLanguages;
String result = WOApplication.application().resourceManager().stringForKey( key, "Localizable", key, framework, languages);
return result;
}
/**
* Uses the method <code>localizedStringForKey</code> to retreive
* a template that is then parsed using the passed in object to
* produce a resulting string. The template parser used is
* {@link ERXSimpleTemplateParser}.
* @param o object used to resolve keys in the localized template
* @param key to be lookup in the strings file
* @param framework name, specify app or null to perform the
* lookup in the application's resources.
* @param languages array to search for the key in
* @return localized template parsed and resolved with the given
* object.
*/
public static String localizedTemplateStringWithObjectForKey(Object o, String key, String framework, NSArray languages) {
String template = localizedStringForKey(key, framework, languages);
return ERXSimpleTemplateParser.sharedInstance().parseTemplateWithObject(template, null, o);
}
/**
* Reads the contents of a file given by a path
* into a string.
* @param file path to the file in the file system
*
* @return the contents of the file in a string
*/
public static String stringWithContentsOfFile(File file) {
try {
if(file != null)
return ERXFileUtilities.stringFromFile(file);
} catch (IOException e) {
log.error("Could not read string from file {}", file, e);
}
return null;
}
/**
* Reads the contents of a file given by a path
* into a string.
* @param path to the file in the file system
* @return the contents of the file in a string
*/
public static String stringWithContentsOfFile(String path) {
if(path != null)
return ERXStringUtilities.stringWithContentsOfFile(new File(path));
return null;
}
/**
* Calculates an Integer for a given string. The
* only advantage that this method has is to not
* throw a number format exception if the string
* is not correctly formatted. This method makes
* use of the ERXConstant.integerForString caching
* logic.
* @param s string to caclulate an Integer from
* @return parsed Integer from the string or null
* if the string is not correctly formed.
* @see er.extensions.eof.ERXConstant#integerForString(String)
*/
public static Integer integerWithString(String s) {
try {
return ERXConstant.integerForInt(Integer.parseInt(s));
} catch (NumberFormatException e) {
// ignore
}
return null;
}
/**
* Tests if a given string object can be parsed into
* an integer.
* @param s string to be parsed
* @return <code>true</code> if the string is not <code>null</code>
* and can be parsed to an int
*/
public static boolean stringIsParseableInteger(String s) {
try {
Integer.parseInt(s);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* Wrapper for {@link Integer#valueOf(String)} that catches
* the NumberFormatException.
*
* @param s string to convert to an Integer
* @return Integer or <code>null</code> if the string could
* not be parsed
*/
public static Integer safeInteger(String s) {
try {
return Integer.valueOf(s);
} catch (NumberFormatException e) {
// ignore
}
return null;
}
/**
* Wrapper for {@link Long#valueOf(String)} that catches
* the NumberFormatException.
*
* @param s string to convert to a Long
* @return Long or <code>null</code> if the string could
* not be parsed
*/
public static Long safeLong(String s) {
try {
return Long.valueOf(s);
} catch (NumberFormatException e) {
// ignore
}
return null;
}
/**
* Retrieves a given string for a given name, extension
* and bundle.
* @param name of the resource
* @param extension of the resource, example: txt or rtf
* @param bundle to look for the resource in
* @return string of the given file specified in the bundle
*/
public static String stringFromResource(String name, String extension, NSBundle bundle) {
String path = null;
if(bundle == null) {
bundle = NSBundle.mainBundle();
}
path = bundle.resourcePathForLocalizedResourceNamed(name + (extension == null || extension.length() == 0 ? "" : "." + extension), null);
if(path != null) {
try (InputStream stream = bundle.inputStreamForResourcePath(path)) {
byte bytes[] = ERXFileUtilities.bytesFromInputStream(stream);
return new String(bytes);
} catch (IOException e) {
log.warn("IOException when stringFromResource({}.{} in bundle {}", name, extension, bundle.name());
}
}
return null;
}
public static final String firstPropertyKeyInKeyPath(String keyPath) {
String part = null;
if (keyPath != null) {
int index = keyPath.indexOf(NSKeyValueCodingAdditions.KeyPathSeparator);
if (index != -1)
part = keyPath.substring(0, index);
else
part = keyPath;
}
return part;
}
public static final String lastPropertyKeyInKeyPath(String keyPath) {
String part = null;
if (keyPath != null) {
int index = keyPath.lastIndexOf(NSKeyValueCodingAdditions.KeyPathSeparator);
if (index != -1)
part = keyPath.substring(index + 1);
else
part = keyPath;
}
return part;
}
public static final String keyPathWithoutLastProperty(String keyPath) {
String part = null;
if(keyPath != null) {
int index = keyPath.lastIndexOf(NSKeyValueCodingAdditions.KeyPathSeparator);
if (index != -1)
part = keyPath.substring(0, index);
}
return part;
}
public static final String keyPathWithoutFirstProperty(String keyPath) {
String part = null;
if(keyPath != null) {
int index = keyPath.indexOf(NSKeyValueCodingAdditions.KeyPathSeparator);
if (index != -1)
part = keyPath.substring(index + 1);
}
return part;
}
/**
* Calculates a default display name for a given
* key path. For instance for the key path:
* "foo.bar" the display name would be "Bar".
* @param key to calculate the display name
* @return display name for the given key
*/
public static String displayNameForKey(String key) {
StringBuilder finalString = null;
if (!stringIsNullOrEmpty(key) && !key.trim().equals("")) {
finalString = new StringBuilder();
String lastHop=key.indexOf(".") == -1 ? key : key.endsWith(".") ? "" : key.substring(key.lastIndexOf(".") + 1);
StringBuilder tempString = new StringBuilder();
char[] originalArray = lastHop.toCharArray();
originalArray[0] = Character.toUpperCase(originalArray[0]);
Character tempChar = null;
Character nextChar = null;
for(int i=0;i<(originalArray.length-1);i++){
tempChar = Character.valueOf(originalArray[i]);
nextChar = Character.valueOf(originalArray[i+1]);
if(Character.isUpperCase(originalArray[i]) &&
Character.isLowerCase(originalArray[i+1])) {
finalString.append(tempString.toString());
if (i>0) finalString.append(' ');
tempString = new StringBuilder();
}
tempString.append(tempChar.toString());
}
finalString.append(tempString.toString());
finalString.append(nextChar);
}
return finalString == null ? "" : finalString.toString();
}
/**
* Locate the the first numeric character in the given string.
*
* @param str string to scan
* @return position in string or -1 if no numeric found
*/
public static int indexOfNumericInString(String str) {
return indexOfNumericInString(str, 0);
}
/**
* Locate the the first numeric character
* after <code>fromIndex</code> in the given string.
*
* @param str string to scan
* @param fromIndex index position from where to start
* @return position in string or -1 if no numeric found
*/
public static int indexOfNumericInString(String str, int fromIndex) {
if (str == null) throw new IllegalArgumentException("String cannot be null.");
int pos = -1;
for (int i = fromIndex; i < str.length(); i++) {
char c = str.charAt(i);
if ('0' <= c && c <= '9') {
pos = i;
break;
}
}
return pos;
}
/**
* Utility method to append a character to a
* StringBuffer if the last character is not
* a certain character. Useful for determining
* if you need to add an '&' to the end of a
* form value string.
* @param separator character to potentially
* add to the StringBuffer.
* @param not character to test if the given
* StringBuffer ends in it.
* @param sb StringBuffer to test and potentially
* append to.
*/
public static void appendSeparatorIfLastNot(char separator, char not, StringBuffer sb) {
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != not)
sb.append(separator);
}
/**
* Utility method to append a character to a
* StringBuilder if the last character is not
* a certain character. Useful for determining
* if you need to add an '&' to the end of a
* form value string.
* @param separator character to potentially
* add to the StringBuilder.
* @param not character to test if the given
* StringBuilder ends in it.
* @param sb StringBuilder to test and potentially
* append to.
*/
public static void appendSeparatorIfLastNot(char separator, char not, StringBuilder sb) {
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != not)
sb.append(separator);
}
/**
* Removes the spaces in a given string.
*
* @param aString string to remove spaces from
* @return string without spaces
*/
public static String removeSpaces(String aString) {
NSArray<String> parts = NSArray.componentsSeparatedByString(aString, " ");
return parts.componentsJoinedByString("");
}
/** This method runs about 20 times faster than
* java.lang.String.toLowerCase (and doesn't waste any storage
* when the result is equal to the input). Warning: Don't use
* this method when your default locale is Turkey.
* java.lang.String.toLowerCase is slow because (a) it uses a
* StringBuffer (which has synchronized methods), (b) it
* initializes the StringBuffer to the default size, and (c) it
* gets the default locale every time to test for name equal to
* "tr".
* @see <a href="http://www.norvig.com/java-iaq.html#tolower">tolower</a>
* @author Peter Norvig **/
public static String toLowerCase(String str) {
if (str == null)
return null;
int len = str.length();
int different = -1;
// See if there is a char that is different in lowercase
for(int i = len-1; i >= 0; i--) {
char ch = str.charAt(i);
if (Character.toLowerCase(ch) != ch) {
different = i;
break;
}
}
// If the string has no different char, then return the string as is,
// otherwise create a lowercase version in a char array.
if (different == -1) {
return str;
}
char[] chars = new char[len];
str.getChars(0, len, chars, 0);
// (Note we start at different, not at len.)
for(int j = different; j >= 0; j--) {
chars[j] = Character.toLowerCase(chars[j]);
}
return new String(chars);
}
/**
* String multiplication.
* @param n the number of times to concatenate a given string
* @param s string to be multiplied
* @return multiplied string
*/
public static String stringWithNtimesString(int n, String s) {
StringBuilder sb = new StringBuilder(n * (s != null ? s.length() : "null".length()));
for (int i=0; i<n; i++) sb.append(s);
return sb.toString();
}
/**
* Counts the number of occurrences of a particular
* <code>char</code> in a given string.
* @param c char to count in string
* @param s string to look for specified char in.
* @return number of occurrences of a char in the string
*/
public static int numberOfOccurrencesOfCharInString(char c, String s) {
int result=0;
if (s!=null) {
for (int i=0; i<s.length();)
if (s.charAt(i++)==c) result++;
}
return result;
}
/**
* Simple test if the string is either null or
* equal to "".
* @param s string to test
* @return result of the above test
*/
public static boolean stringIsNullOrEmpty(String s) {
return ((s == null) || (s.length() == 0));
}
/**
* Simple utility method that will return null
* if the string passed in is equal to ""
* otherwise it will return the passed in
* string.
* @param s string to test
* @return null if the string is "" else the string.
*/
public static String nullForEmptyString(String s) {
return s==null ? null : (s.length()==0 ? null : s);
}
/**
* Simple utility method that will return the
* string "" if the string passed in is null
* otherwise it will return the passed in
* string.
* @param s string to test
* @return the empty string if the string is null, else the string
*/
public static String emptyStringForNull(String s) {
return s==null ? "" : s;
}
public static String escapeNonXMLChars(String str) {
if (str == null) return null;
StringBuilder result = new StringBuilder(str.length());
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
switch(c) {
case '<': result.append("<"); break;
case '>': result.append(">"); break;
case '&': result.append("&"); break;
case '"': result.append("""); break;
default:
result.append(c);
}
}
return result.toString();
}
/**
* XML entities to unescape.
*/
public static final NSDictionary<String, String> XML_UNESCAPES;
/**
* ISO entities to unescape.
*/
public static final NSDictionary<String, String> ISO_UNESCAPES;
/**
* Symbol entities to unescape.
*/
public static final NSDictionary<String, String> SYMBOL_UNESCAPES;
/**
* Safe HTML entities to unescape (SYMBOL+ISO). This still prevents injection attacks.
*/
public static final NSDictionary<String, String> HTML_SAFE_UNESCAPES;
/**
* HTML entities to unescape (XML+SYMBOL+ISO).
*/
public static final NSDictionary<String, String> HTML_UNESCAPES;
static {
// NOTE AK I used:
// http://www.w3schools.com/tags/ref_symbols.asp
// http://www.w3schools.com/tags/ref_entities.asp
// as apache commons lang didn't really work for me?!?
Object[] xml = new Object[] { '<', "lt", '>', "gt", '&', "amp", '\"', "quot" };
NSMutableDictionary<String, String> dict = new NSMutableDictionary<>();
for (int i = 0; i < xml.length; i+=2) {
Character charValue = ((Character) xml[i]);
String key = (String) xml[i+1];
dict.setObjectForKey(charValue+"", key);
dict.setObjectForKey(charValue+"", "#"+charValue);
}
XML_UNESCAPES = dict.immutableClone();
Object[] iso = { 160, "nbsp", 161, "iexcl", 162, "cent", 163, "pound", 164, "curren", 165, "yen", 166, "brvbar", 167, "sect", 168, "uml", 169, "copy", 170, "ordf", 171, "laquo", 172, "not", 173, "shy", 174, "reg", 175, "macr", 176, "deg", 177, "plusmn", 178, "sup2", 179, "sup3", 180, "acute", 181, "micro", 182, "para", 183, "middot", 184, "cedil", 185, "sup1", 186, "ordm", 187, "raquo", 188, "frac14", 189, "frac12", 190, "frac34", 191, "iquest", 215, "times", 247, "divide", 192, "Agrave", 193, "Aacute", 194, "Acirc", 195, "Atilde", 196, "Auml", 197, "Aring", 198, "AElig", 199, "Ccedil", 200, "Egrave", 201, "Eacute", 202, "Ecirc", 203, "Euml", 204, "Igrave", 205, "Iacute", 206, "Icirc", 207, "Iuml", 208, "ETH", 209, "Ntilde", 210, "Ograve", 211, "Oacute", 212, "Ocirc", 213, "Otilde", 214, "Ouml", 216, "Oslash", 217, "Ugrave", 218, "Uacute", 219, "Ucirc", 220, "Uuml", 221, "Yacute", 222, "THORN", 223, "szlig", 224, "agrave", 225, "aacute", 226, "acirc", 227, "atilde", 228,
"auml", 229, "aring", 230, "aelig", 231, "ccedil", 232, "egrave", 233, "eacute", 234, "ecirc", 235, "euml", 236, "igrave", 237, "iacute", 238, "icirc", 239, "iuml", 240, "eth", 241, "ntilde", 242, "ograve", 243, "oacute", 244, "ocirc", 245, "otilde", 246, "ouml", 248, "oslash", 249, "ugrave", 250, "uacute", 251, "ucirc", 252, "uuml", 253, "yacute", 254, "thorn", 255, "yuml" };
dict = new NSMutableDictionary<>();
for (int i = 0; i < iso.length; i+=2) {
Integer charValue = ((Integer) iso[i]);
String key = (String) iso[i+1];
dict.setObjectForKey(Character.toChars(charValue)[0]+"", key);
dict.setObjectForKey(Character.toChars(charValue)[0]+"", "#"+charValue);
}
ISO_UNESCAPES = dict.immutableClone();
Object[] symbols = new Object[] { 8704, "forall", 8706, "part", 8707, "exists", 8709, "empty", 8711, "nabla", 8712, "isin", 8713, "notin", 8715, "ni", 8719, "prod", 8721, "sum", 8722, "minus", 8727, "lowast", 8730, "radic", 8733, "prop", 8734, "infin", 8736, "ang", 8743, "and", 8744, "or", 8745, "cap", 8746, "cup", 8747, "int", 8756, "there4", 8764, "sim", 8773, "cong", 8776, "asymp", 8800, "ne", 8801, "equiv", 8804, "le", 8805, "ge", 8834, "sub", 8835, "sup", 8836, "nsub", 8838, "sube", 8839, "supe", 8853, "oplus", 8855, "otimes", 8869, "perp", 8901, "sdot", 913, "Alpha", 914, "Beta", 915, "Gamma", 916, "Delta", 917, "Epsilon", 918, "Zeta", 919, "Eta", 920, "Theta", 921, "Iota", 922, "Kappa", 923, "Lambda", 924, "Mu", 925, "Nu", 926, "Xi", 927, "Omicron", 928, "Pi", 929, "Rho", 931, "Sigma", 932, "Tau", 933, "Upsilon", 934, "Phi", 935, "Chi", 936, "Psi", 937, "Omega", 945, "alpha", 946, "beta", 947, "gamma", 948, "delta", 949, "epsilon", 950, "zeta", 951, "eta", 952, "theta",
953, "iota", 954, "kappa", 955, "lambda", 956, "mu", 957, "nu", 958, "xi", 959, "omicron", 960, "pi", 961, "rho", 962, "sigmaf", 963, "sigma", 964, "tau", 965, "upsilon", 966, "phi", 967, "chi", 968, "psi", 969, "omega", 977, "thetasym", 978, "upsih", 982, "piv", 338, "OElig", 339, "oelig", 352, "Scaron", 353, "scaron", 376, "Yuml", 402, "fnof", 710, "circ", 732, "tilde", 8194, "ensp", 8195, "emsp", 8201, "thinsp", 8204, "zwnj", 8205, "zwj", 8206, "lrm", 8207, "rlm", 8211, "ndash", 8212, "mdash", 8216, "lsquo", 8217, "rsquo", 8218, "sbquo", 8220, "ldquo", 8221, "rdquo", 8222, "bdquo", 8224, "dagger", 8225, "Dagger", 8226, "bull", 8230, "hellip", 8240, "permil", 8242, "prime", 8243, "Prime", 8249, "lsaquo", 8250, "rsaquo", 8254, "oline", 8364, "euro", 8482, "trade", 8592, "larr", 8593, "uarr", 8594, "rarr", 8595, "darr", 8596, "harr", 8629, "crarr", 8968, "lceil", 8969, "rceil", 8970, "lfloor", 8971, "rfloor", 9674, "loz", 9824, "spades", 9827, "clubs", 9829, "hearts",
9830, "diams" };
dict = new NSMutableDictionary<>();
for (int i = 0; i < symbols.length; i+=2) {
Integer charValue = ((Integer) symbols[i]);
String key = (String) symbols[i+1];
dict.setObjectForKey(Character.toChars(charValue)[0]+"", key);
dict.setObjectForKey(Character.toChars(charValue)[0]+"", "#"+charValue);
}
SYMBOL_UNESCAPES = dict.immutableClone();
dict = new NSMutableDictionary<>();
dict.addEntriesFromDictionary(ISO_UNESCAPES);
dict.addEntriesFromDictionary(SYMBOL_UNESCAPES);
HTML_SAFE_UNESCAPES = dict.immutableClone();
dict.addEntriesFromDictionary(XML_UNESCAPES);
HTML_UNESCAPES = dict.immutableClone();
}
/**
* Util to unescape entities. Entities not found in the set will be left intact.
* @param string string to unescape
* @param map map of entities
* @return unescaped string
*/
public static String unescapeEntities(String string, Map<String, String> map) {
if(string != null) {
StringBuilder result = new StringBuilder();
int len = string.length();
for(int start = 0; start < len; start++) {
char c1 = string.charAt(start);
if(c1 == '&') {
StringBuilder entity = new StringBuilder();
for(int end = start+1; end < len; end++) {
char c2 = string.charAt(end);
if(c2 == ';') {
String key = entity.toString();
String replacement = map.get(key);
if(replacement == null) {
replacement = map.get(key.toUpperCase());
}
if(replacement == null) {
replacement = "&" + key + ";";
}
result.append(replacement);
start = end;
break;
}
entity.append(c2);
}
} else {
result.append(c1);
}
}
string = result.toString();
}
return string;
}
/**
* Escapes the given PCDATA string as CDATA.
* @param pcdata The string to escape
* @return the escaped string
*/
public static String escapePCData(String pcdata) {
if(pcdata == null) { return null; }
int start = 0;
int end = 0;
String close = "]]>";
String escape = "]]]]><![CDATA[>";
StringBuilder sb = new StringBuilder("<![CDATA[");
do {
end = pcdata.indexOf(close, start);
sb.append(pcdata.substring(start, (end==-1?pcdata.length():end)));
if(end != -1) { sb.append(escape); }
start = end;
start += 3;
} while (end != -1);
sb.append(close);
return sb.toString();
}
public static String escapeNonBasicLatinChars(char c) {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
if (block != null && Character.UnicodeBlock.BASIC_LATIN.equals(block))
return String.valueOf(c);
return toHexString(c);
}
public static String escapeNonBasicLatinChars(String str) {
if (str == null) return null;
StringBuilder result = new StringBuilder(str.length());
for (int i = 0; i < str.length(); i++)
result.append(escapeNonBasicLatinChars(str.charAt(i)));
return result.toString();
}
/**
* Escapes the apostrophes in a Javascript string with a backslash.
*
* @param sourceString the source string to escape
* @return the escaped javascript string
*/
public static String escapeJavascriptApostrophes(String sourceString) {
return ERXStringUtilities.escape(new char[] { '\'' }, '\\', sourceString);
}
/**
* Escapes the given characters with the given escape character in _sourceString. This
* implementation is specifically designed for large strings. In the event that no characters
* are escaped, the original string will be returned with no new object creation. A null
* _sourceString will return null.
*
* Example: sourceString = Mike's, escape chars = ', escape with = \, returns Mike\'s
*
* @param _escapeChars the list of characters to escape
* @param _escapeWith the escape character to use
* @param _sourceString the string to escape the characters in.
* @return the escaped string
*/
public static String escape(char[] _escapeChars, char _escapeWith, String _sourceString) {
String targetString;
if (_sourceString == null) {
targetString = null;
}
else {
StringBuilder targetBuffer = null;
int lastMatch = 0;
int length = _sourceString.length();
for (int sourceIndex = 0; sourceIndex < length; sourceIndex++) {
char ch = _sourceString.charAt(sourceIndex);
boolean escape = false;
for (int escapeNum = 0; !escape && escapeNum < _escapeChars.length; escapeNum++) {
if (ch == _escapeChars[escapeNum]) {
escape = true;
}
}
if (escape) {
if (targetBuffer == null) {
targetBuffer = new StringBuilder(length + 100);
}
if (sourceIndex - lastMatch > 0) {
targetBuffer.append(_sourceString.substring(lastMatch, sourceIndex));
}
targetBuffer.append(_escapeWith);
lastMatch = sourceIndex;
}
}
if (targetBuffer == null) {
targetString = _sourceString;
}
else {
targetBuffer.append(_sourceString.substring(lastMatch, length));
targetString = targetBuffer.toString();
}
}
return targetString;
}
public static String toHexString(char c) {
StringBuilder result = new StringBuilder("\u005C\u005Cu9999".length());
String u = Long.toHexString(c).toUpperCase();
switch (u.length()) {
case 1: result.append("\u005C\u005Cu000"); break;
case 2: result.append("\u005C\u005Cu00"); break;
case 3: result.append("\u005C\u005Cu0"); break;
default: result.append("\u005C\u005Cu"); break;
}
result.append(u);
return result.toString();
}
public static String toHexString(String str) {
if (str == null) return null;
StringBuilder result = new StringBuilder("\u005C\u005Cu9999".length() * str.length());
for (int i = 0; i < str.length(); i++)
result.append(toHexString(str.charAt(i)));
return result.toString();
}
/**
* Converts a byte array to hex string.
* @param block byte array
* @return hex string
*/
public static String byteArrayToHexString(byte[] block) {
int len = block.length;
StringBuilder buf = new StringBuilder(2 * len);
for (int i = 0; i < len; ++i) {
int high = ((block[i] & 0xf0) >> 4);
int low = (block[i] & 0x0f);
buf.append(HEX_CHARS[high]);
buf.append(HEX_CHARS[low]);
}
return buf.toString();
}
/**
* Converts a even-length, hex-encoded String to a byte array.
*
* @param hexString hex string to convert
* @return byte array of given hex string
*/
public static byte[] hexStringToByteArray(String hexString) {
int length = hexString.length();
if(length % 2 == 1) {
throw new IllegalArgumentException("String must have even length: " + length);
}
byte array[] = new byte[length/2];
for(int i = 0; i < array.length; i++) {
char c1 = hexString.charAt(i*2);
char c2 = hexString.charAt(i*2+1);
byte b = 0;
if(c1 >= '0' && c1 <= '9')
b += (c1 - 48) * 16;
else if(c1 >= 'a' && c1 <= 'f')
b += ((c1 - 97) + 10) * 16;
else if(c1 >= 'A' && c1 <= 'F')
b += ((c1 - 65) + 10) * 16;
else
throw new IllegalArgumentException("Illegal Character: '" + c1 + "' in " + hexString);
if(c2 >= '0' && c2 <= '9')
b += c2 - 48;
else if(c2 >= 'a' && c2 <= 'f')
b += (c2 - 97) + 10;
else if(c2 >= 'A' && c2 <= 'F')
b += (c2 - 65) + 10;
else
throw new IllegalArgumentException("Illegal Character: '" + c2 + "' in " + hexString);
array[i] = b;
}
return array;
}
/**
* Cleans up the given version string by removing extra
* dots(.), for example, 5.1.3 becomes 5.13, so that
* the string can be converted to a double or BigDecimal
* type easily.
*
* @param version string
* @return cleaned-up string that only contains the
* first dot(.) as the floating point indicator.
*/
public static String removeExtraDotsFromVersionString(String version) {
int floatingPointIndex = version.indexOf(".");
if (floatingPointIndex >= 0 && floatingPointIndex + 1 < version.length()) {
String minorVersion = StringUtils.replace(version.substring(floatingPointIndex + 1), ".", "");
version = version.substring(0, floatingPointIndex + 1) + minorVersion;
}
return version;
}
/**
* Capitalizes a given string. That is, the first character of the returned
* string will be upper case, and other characters will be unchanged. For
* example, for the input string "{@code you have a dog}", this method would
* return "{@code You have a dog}".
*
* @param value to be capitalized
* @return capitalized string
*/
public static String capitalize(String value) {
String capital = null;
if (value != null && value.length() > 0) {
StringBuilder buffer = new StringBuilder(value);
buffer.setCharAt(0, Character.toUpperCase(value.charAt(0)));
capital = buffer.toString();
}
return capital != null ? capital : value;
}
/**
* Uncapitalizes a given string.
* @param value to be uncapitalized
* @return capitalized string
*/
public static String uncapitalize(String value) {
String capital = null;
if (value != null) {
int length = value.length();
if (length > 0) {
StringBuilder buffer = new StringBuilder(value);
for (int i = 0; i < length; i ++) {
char ch = value.charAt(i);
if (i == 0 || i == length - 1 || (i < length - 1 && Character.isUpperCase(value.charAt(i + 1)))) {
buffer.setCharAt(i, Character.toLowerCase(ch));
}
else {
break;
}
}
capital = buffer.toString();
}
}
return capital != null ? capital : value;
}
/**
* Capitalizes all the strings in a given string. That is, the first
* character of each (whitespace-delimited) word in the input string will be
* upper case, and other characters will be unchanged. Additionally, each
* region of contiguous whitespace in the original string is converted to a
* single space in the result. For example, for the input string
* "{@code you have a dog}" (with two spaces between each word), this
* method would return "{@code You Have A Dog}".
*
* @param value to be capitalized
* @return capitalized string
*/
public static String capitalizeAllWords(String value) {
String capitalize = null;
if (value != null && value.length() > 0) {
StringBuilder buffer = new StringBuilder();
boolean first = true;
for (StringTokenizer tokenizer = new StringTokenizer(value); tokenizer.hasMoreElements();) {
String token = tokenizer.nextToken();
if (!first) {
buffer.append(' ');
} else {
first = false;
}
buffer.append(capitalize(token));
}
capitalize = buffer.toString();
}
return capitalize != null ? capitalize : value;
}
/**
* Converts this_is_a_test to ThisIsATest
* @param underscoreString the string_with_underscores
* @param capitalize if true, the first letter is capitalized
* @return the StringWithoutUnderscores
*/
public static String underscoreToCamelCase(String underscoreString, boolean capitalize) {
StringBuilder camelCase = new StringBuilder();
String[] underscoreStrings = underscoreString.split("_");
for (int i = 0; i < underscoreStrings.length; i ++) {
String word;
if (i > 0 || capitalize) {
word = ERXStringUtilities.capitalize(underscoreStrings[i]);
}
else {
word = underscoreStrings[i];
}
camelCase.append(word);
}
return camelCase.toString();
}
/**
* Converts a string in camel case to an underscore representation.
*
* @param camelString string to convert
* @param lowercase if all uppercase characters should be converted to lowercase
* @return the string_with_underscores
*/
public static String camelCaseToUnderscore(String camelString, boolean lowercase) {
StringBuilder underscore = new StringBuilder();
boolean lastCharacterWasWordBreak = false;
boolean lastCharacterWasCapital = false;
int length = camelString.length();
for (int i = 0; i < length; i ++) {
char ch = camelString.charAt(i);
if (Character.isUpperCase(ch)) {
boolean isLastCharacter = (i == length - 1);
boolean nextCharacterIsCapital = (!isLastCharacter && Character.isUpperCase(camelString.charAt(i + 1)));
if (i > 0 && ((!lastCharacterWasWordBreak && !lastCharacterWasCapital) || (!nextCharacterIsCapital && !isLastCharacter))) {
underscore.append('_');
lastCharacterWasWordBreak = true;
}
else {
lastCharacterWasWordBreak = false;
}
lastCharacterWasCapital = true;
}
else if (ch == '_') {
lastCharacterWasWordBreak = true;
lastCharacterWasCapital = false;
}
else {
lastCharacterWasWordBreak = false;
lastCharacterWasCapital = false;
}
if (lowercase) {
underscore.append(Character.toLowerCase(ch));
}
else {
underscore.append(ch);
}
}
return underscore.toString();
}
public static boolean stringEqualsString(String s1, String s2) {
if (s1 == s2) return true;
if (s1 != null && s2 != null && s1.equals(s2)) return true;
if (s1 == null && s2 == null) return true;
return false;
}
/**
* Tests if the string starts with the specified prefix ignoring case. This method is
* optimized so that it only converts the relevant substring of stringToSearch to lowercase
* before comparing it to the lowercase version of prefix.
* @param stringToSearch string to check
* @param prefix prefix to look for
* @return true if stringToSearch case-insensitively starts with prefix
*/
public static boolean caseInsensitiveStartsWith(String stringToSearch, String prefix) {
return caseInsensitiveStartsWith(stringToSearch, prefix, 0);
}
/**
* Tests if the string starts with the specified prefix starting at the specified index ignoring case.
* This method is optimized so that it only converts the relevant substring of stringToSearch to lowercase
* before comparing it to the lowercase version of prefix.
* @param stringToSearch string to check
* @param prefix prefix to look for
* @param toffset starting offset to perform the search
* @return true if stringToSearch case-insensitively starts with prefix starting at toffset
*/
public static boolean caseInsensitiveStartsWith(String stringToSearch, String prefix, int toffset) {
boolean result = false;
final int stringToSearchLength = stringToSearch.length();
final int prefixLength = prefix.length();
if ( (toffset + prefixLength) <= stringToSearchLength ) {
final String slice = stringToSearch.substring(toffset, toffset+prefixLength);
result = toLowerCase(slice).equals(toLowerCase(prefix));
}
return result;
}
/**
* This method takes a string and returns a string which is the first string such that the
* result byte length in the specified encoding does not exceed the byte limit. This tends
* to be an issue with UTF-8 and Japanese characters because they're double- or triple-byte
* in UTF-8 and you need to be careful not to split in the middle of a multi-byte sequence.
*
* <p>
*
* This method is optimized for the UTF-8 case. If <code>encoding</code> is either "UTF-8" or "UTF8",
* the optimized case will kick in.
*
* @param inputString string to truncate
* @param byteLength maximum byte length
* @param encoding encoding to use
* @return string truncated appropriately.
*/
public static String stringByTruncatingStringToByteLengthInEncoding(final String inputString, final int byteLength, final String encoding) {
String result = null;
log.debug("stringByTruncatingStringToByteLengthInEncoding: encoding='{}', byteLength={}, inputString='{}'", encoding, byteLength, inputString);
if ( inputString != null ) {
final byte[] bytes = toBytes(inputString, encoding);
if ( bytes != null ) {
if ( bytes.length > byteLength ) {
// we gotta do some work
if ( "UTF-8".equals(encoding) || "UTF8".equals(encoding) ) {
// if the encoding is UTF-8, we can be smarter about this than in the general case.
// UTF-8 defines three types of bytes: those that begin in 0, those that begin in 11 and
// those that begin in 10. 0 means ASCII, 11 means the beginning of a multi-byte sequence,
// 10 means in the middle or end of a multi-byte sequence.
//
// so, we hit the byte at index byteLength which is one past our target byte length. while
// that byte begins with 10, we walk backwards until the next byte doesn't begin with 10. at that
// point, we're at the end of a character.
if ( byteLength >= 1 ) {
int index = byteLength - 1;
while ( index >= 0 && ((bytes[index+1] & 0xC0) == 0x80) )
index--;
if ( index > 0 ) {
try {
result = new String(bytes, 0, index+1, encoding);
}
catch ( UnsupportedEncodingException e ) {
throw new RuntimeException("Got " + e.getClass() + " exception. byteLength=" + byteLength + ", encoding='" +
encoding + "', inputString='"+ inputString + "'.", e);
}
}
}
result = result != null ? result : "";
}
else {
final Charset charset = Charset.forName(encoding);
final CharsetDecoder decoder = charset.newDecoder();
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
int currentLength = byteLength;
CharBuffer charBuffer = null;
do {
byteBuffer.position(0);
byteBuffer.limit(currentLength);
try {
charBuffer = decoder.reset().decode(byteBuffer);
}
catch ( MalformedInputException e ) {
// this exception we expect to get if when we slice the byte buffer to
// the intended byte length, we end up mid-way through a byte sequence.
currentLength--;
}
catch (CharacterCodingException e ) {
// we're not expecting to get this exception. the javadoc doesn't say
// under what circumstances it happens and it would be surprising it this happened
// and the conversion to byte array worked earlier.
log.error("Got {} exception. byteLength={}, encoding='{}', inputString='{}'.",
e.getClass(), byteLength, encoding, inputString, e);
break;
}
} while ( charBuffer == null && currentLength > 0 );
result = charBuffer != null ? charBuffer.toString() : "";
}
}
else {
result = inputString;
}
}
}
log.debug("stringByTruncatingStringToByteLengthInEncoding: result='{}'", result);
return result;
}
/** checks if the String contains a character that has a special meaning
* in regex. This could used to ensure that username and passwords have no
* such characters.
*
* @param s the string to check
* @return <code>true</code> if s contains one or multiple characters that have special
* meanings in regex.
*/
public static boolean stringContainsSpecialRegexCharacters(String s) {
for (int i = 0; i < s.length(); i++) {
if (SpecialRegexCharacters.indexOf(s.charAt(i)) > -1) { return true; }
}
return false;
}
/**
* Returns a string from the contents of the given URL.
*
* @param url the URL to read from
* @return the string that was read
* @throws IOException if the connection fails
*/
public static String stringFromURL(URL url) throws IOException {
try (InputStream is = url.openStream()) {
return ERXStringUtilities.stringFromInputStream(is);
}
}
/**
* Returns a string from the contents of the given URL.
*
* @param url the URL to read from
* @param encoding the string encoding to read with
*
* @return the string that was read
* @throws IOException if the connection fails
*/
public static String stringFromURL(URL url, String encoding) throws IOException {
try (InputStream is = url.openStream()) {
return ERXStringUtilities.stringFromInputStream(is, encoding);
}
}
/**
* Returns a string from the input stream using the default
* encoding.
* @param in stream to read
* @return string representation of the stream.
* @throws IOException if things go wrong
*/
public static String stringFromInputStream(InputStream in) throws IOException {
return new String(ERXFileUtilities.bytesFromInputStream(in));
}
/**
* Returns a string from the input stream using the default
* encoding.
* @param in stream to read
* @param encoding to be used, null will use the default
* @return string representation of the stream.
* @throws IOException if things go wrong
*/
public static String stringFromInputStream(InputStream in, String encoding) throws IOException {
return new String(ERXFileUtilities.bytesFromInputStream(in), encoding);
}
/**
* Returns a String by invoking toString() on each object from the array. After each toString() call
* the separator is appended to the buffer.
*
* @param array an object array from which to get a nice String representation
* @param separator a separator which is displayed between the objects toString() value
*
* @return a string representation from the array
*/
public static String toString(Object[] array, String separator) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < array.length; i++) {
Object o = array[i];
buf.append(o.toString());
buf.append(separator);
}
return buf.toString();
}
/**
* Creates a readable debug string for some data types (dicts, arrays, adaptorOperations, databaseOperations).
*
* @param object the object to dump
* @return string representation of the given object
*/
public static String dumpObject(Object object) {
StringBuffer sb = new StringBuffer(4000);
dumpObject(sb, object, 0);
return sb.toString();
}
/**
* Checks if any of the characters specified in characters is contained in the string
* specified by source.
*
* @param source the String which might contain characters
* @param characters the characters to check
* @return true if any character from characters is in source, false otherwise
*/
public static boolean containsAnyCharacter(String source, String characters) {
for (int i = source.length(); i-- > 0;) {
char c = source.charAt(i);
if (characters.indexOf(c) > -1) {
return true;
}
}
return false;
}
/**
* Removes any character which is not in characters from the source string.
*
* @param source the string which will be modified
* @param characters the characters that are allowed to be in source
* @return a new string only with characters from the characters argument
*/
public static String removeExceptCharacters(String source, String characters) {
StringBuilder buf = new StringBuilder();
int l = source.length();
for (int i = 0; i < l; i++) {
char c = source.charAt(i);
if (characters.indexOf(c) > -1) {
buf.append(c);
}
}
return buf.toString();
}
/**
* Removes any character which is in characters from the source string.
*
* @param source the string which will be modified
* @param characters the characters that are not allowed to be in source
* @return a new string without any characters from the characters argument
*/
public static String removeCharacters(String source, String characters) {
StringBuilder buf = new StringBuilder();
int l = source.length();
for (int i = 0; i < l; i++) {
char c = source.charAt(i);
if (characters.indexOf(c) == -1) {
buf.append(c);
}
}
return buf.toString();
}
/**
* Matches strings like Quicksilver (NullPointerException is matched by "NPE"). The rule
* is basically just that all the letters of the search string must appear in the original
* string in the same order as the search string, but they are not required to be contiguous
* or case-matched.
*
* @param _str the string to search in
* @param _searchString the search string to look for
* @return whether or not _str contains _searchString
*/
public static boolean quicksilverContains(String _str, String _searchString) {
boolean equals;
if (_str == null) {// || _searchString == null || _searchString.length() == 0) {
equals = false;
}
else {
equals = true;
if (_searchString != null && _searchString.length() > 0) {
int searchStringLength = _searchString.length();
int strLength = _str.length();
int strPos = 0;
for (int searchStringPos = 0; equals && searchStringPos < searchStringLength; searchStringPos++) {
char searchStringCh = Character.toLowerCase(_searchString.charAt(searchStringPos));
boolean searchStringChFound = false;
for (; !searchStringChFound && strPos < strLength; strPos++) {
char strCh = _str.charAt(strPos);
searchStringChFound = (Character.toLowerCase(strCh) == searchStringCh);
}
if (!searchStringChFound) {
equals = false;
}
}
}
}
return equals;
}
/**
* Generate an MD5 hash from a String.
*
* @param str the string to hash
* @param encoding MD5 operates on byte arrays, so we need to know the encoding to getBytes as
* @return the MD5 sum of the bytes
*/
public static byte[] md5(String str, String encoding) {
byte[] bytes;
if (str == null) {
bytes = new byte[0];
}
else {
try {
if(encoding == null) {
encoding = CharEncoding.UTF_8;
}
bytes = ERXFileUtilities.md5(new ByteArrayInputStream(str.getBytes(encoding)));
}
catch (UnsupportedEncodingException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
catch (IOException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
return bytes;
}
/**
* Generate an MD5 hash as hex from a String.
*
* @param str the string to hash
* @param encoding MD5 operates on byte arrays, so we need to know the encoding to getBytes as
* @return the MD5 sum of the bytes in a hex string
*/
public static String md5Hex(String str, String encoding) {
String hexStr;
if (str == null) {
hexStr = null;
}
else {
hexStr = ERXStringUtilities.byteArrayToHexString(ERXStringUtilities.md5(str, encoding));
}
return hexStr;
}
/**
* Returns a string case-matched against the original string. For instance, if originalString
* is "Mike" and newString is "john", this returns "John". If originalString is "HTTP" and
* newString is "something", this returns "SOMETHING".
*
* @param originalString the original string to analyze the case of
* @param newString the new string
* @return the case-matched variant of newString
*/
public static String matchCase(String originalString, String newString) {
String matchedCase = newString;
if (matchedCase != null) {
int length = originalString.length();
if (length > 0) {
boolean uppercase = true;
boolean lowercase = true;
boolean capitalize = true;
for (int i = 0; i < length; i ++) {
char ch = originalString.charAt(i);
if (Character.isUpperCase(ch)) {
lowercase = false;
if (i > 0) {
capitalize = false;
}
}
else {
uppercase = false;
if (i == 0) {
capitalize = false;
}
}
}
if (capitalize) {
matchedCase = ERXStringUtilities.capitalize(newString);
}
else if (uppercase) {
matchedCase = newString.toUpperCase();
}
else if (lowercase) {
matchedCase = newString.toLowerCase();
}
}
}
return matchedCase;
}
// ##########################################################################################
// private methods
// ##########################################################################################
public static void indent(PrintWriter writer, int level) {
for (int i = 0; i < level; i++) {
writer.append(" ");
}
}
public static void indent(StringBuffer sb, int level) {
for (int i = 0; i < level; i++) {
sb.append(" ");
}
}
private static void dumpArray(StringBuffer sb, NSArray array, int level) {
sb.append("(\n");
for(Enumeration e = array.objectEnumerator(); e.hasMoreElements();) {
Object value = e.nextElement();
dumpObject(sb, value, level+1);
sb.append(",\n");
}
indent(sb, level);
sb.append(')');
}
private static void dumpDictionary(StringBuffer sb, NSDictionary dict, int level) {
sb.append("{\n");
for(Enumeration e = dict.keyEnumerator(); e.hasMoreElements();) {
Object key = e.nextElement();
Object value = dict.objectForKey(key);
indent(sb, level+1);
sb.append(key).append(" = ");
dumpObject(sb, value, level+1);
sb.append(";\n");
}
indent(sb, level);
sb.append('}');
}
private static NSDictionary<String, Object> databaseOperationAsDictionary(EODatabaseOperation op) {
NSMutableDictionary<String, Object> dict = new NSMutableDictionary<>(8);
int operator = op.databaseOperator();
if(operator == 0) {
dict.setObjectForKey("EODatabaseNothingOperator", "_databaseOperator");
} else if(operator == 1) {
dict.setObjectForKey("EODatabaseInsertOperator", "_databaseOperator");
} else if(operator == 3) {
dict.setObjectForKey("EODatabaseDeleteOperator", "_databaseOperator");
} else if(operator == 2) {
dict.setObjectForKey("EODatabaseUpdateOperator", "_databaseOperator");
} else {
dict.setObjectForKey("<unrecognized value>", "_databaseOperator");
}
if(op.newRow() != null)
dict.setObjectForKey(op.newRow(), "_newRow");
if(op.dbSnapshot() != null)
dict.setObjectForKey(op.dbSnapshot(), "_dbSnapshot");
if(op.globalID() != null)
dict.setObjectForKey(op.globalID(), "_globalID");
if(op.entity() != null)
dict.setObjectForKey(op.entity().name(), "_entity");
if(op.adaptorOperations() != null)
dict.setObjectForKey(op.adaptorOperations(), "_adaptorOps");
if(op.object() != null)
dict.setObjectForKey(op.object().toString(), "_object");
return dict;
}
/**
* Debug method to get the EOAdaptorOperation as a dictionary that can be pretty-printed later.
* The output from a EOGeneralAdaptorException.userInfo.toString is pretty much unreadable.
* @param op
*/
private static NSDictionary<String, Object> adaptorOperationAsDictionary(EOAdaptorOperation op) {
NSMutableDictionary<String, Object> dict = new NSMutableDictionary<>();
int operator = op.adaptorOperator();
if(operator == 0) {
dict.setObjectForKey("EOAdaptorLockOperator", "_adaptorOperator");
} else if (operator == 1) {
dict.setObjectForKey("EOAdaptorInsertOperator", "_adaptorOperator");
} else if (operator == 3) {
dict.setObjectForKey("EOAdaptorDeleteOperator", "_adaptorOperator");
} else if (operator == 2) {
dict.setObjectForKey("EOAdaptorUpdateOperator", "_adaptorOperator");
} else {
dict.setObjectForKey("<unrecognized value>", "_adaptorOperator");
}
if(op.entity() != null)
dict.setObjectForKey(op.entity(), "_entity");
if(op.qualifier() != null)
dict.setObjectForKey(op.qualifier().toString(), "_qualifier");
if(op.changedValues() != null)
dict.setObjectForKey(op.changedValues(), "_changedValues");
if(op.exception() != null)
dict.setObjectForKey(op.exception(), "_exception");
return dict;
}
private static void dumpObject(StringBuffer sb, Object value, int level) {
if(value instanceof NSDictionary) {
dumpDictionary(sb, (NSDictionary)value, level);
} else if (value instanceof NSArray) {
dumpArray(sb, (NSArray)value, level);
} else if (value instanceof NSData) {
NSData data = (NSData)value;
sb.append(byteArrayToHexString(data.bytes()));
} else if (value instanceof EODatabaseOperation) {
dumpDictionary(sb, databaseOperationAsDictionary((EODatabaseOperation)value), level);
} else if (value instanceof EOAdaptorOperation) {
dumpDictionary(sb, adaptorOperationAsDictionary((EOAdaptorOperation)value), level);
} else {
indent(sb, level);
sb.append(value);
}
}
/**
* "Borrowed" from 1.5's Class.getSimpleBinaryName
*/
private static String getSimpleBinaryName(Class clazz) {
Class declaringClass = clazz.getDeclaringClass();
if (declaringClass == null) {
return null;
}
try {
return clazz.getName().substring(declaringClass.getName().length());
}
catch (IndexOutOfBoundsException e) {
throw new InternalError("Malformed class name");
}
}
/**
* "Borrowed" from 1.5's Class.getSimpleClassName
*/
public static String getSimpleClassName(Class clazz) {
if (clazz.isArray()) {
return ERXStringUtilities.getSimpleClassName(clazz.getComponentType()) + "[]";
}
String declaringClassName = ERXStringUtilities.getSimpleBinaryName(clazz);
if (declaringClassName == null) {
declaringClassName = clazz.getName();
return declaringClassName.substring(declaringClassName.lastIndexOf(".") + 1);
}
int i = declaringClassName.length();
if (i < 1 || declaringClassName.charAt(0) != '$') {
throw new InternalError("Malformed class name");
}
int j;
for (j = 1; j < i && CharUtils.isAsciiNumeric(declaringClassName.charAt(j)); j++) {
}
return declaringClassName.substring(j);
}
/**
* Same as NSPropertySerialization except it sorts on keys first.
* @param dict
*/
public static String stringFromDictionary(NSDictionary dict) {
NSArray orderedKeys = dict.allKeys();
orderedKeys = ERXArrayUtilities.sortedArraySortedWithKey(orderedKeys, "toString.toLowerCase");
StringBuilder result = new StringBuilder();
for (Enumeration keys = orderedKeys.objectEnumerator(); keys.hasMoreElements();) {
Object key = keys.nextElement();
Object value = dict.objectForKey(key);
String stringValue = NSPropertyListSerialization.stringFromPropertyList(value);
String stringKey = NSPropertyListSerialization.stringFromPropertyList(key);
if(!(value instanceof String)) {
stringValue = stringValue.replaceAll("\n", "\n\t");
}
result.append('\t');
result.append(stringKey);
result.append(" = ");
result.append(stringValue);
result.append(";\n");
}
return "{\n" + result + "}\n";
}
/**
* It's ridiculous that StringBuffer doesn't have a .regionMatches like String. This is
* stolen from String and re-implemented on top of StringBuffer. It's slightly slower than
* String's because we have to call charAt instead of just accessing the underlying array,
* but so be it.
*
* @param str the StringBuffer to compare a region of
* @param toffset the starting offset of the sub-region in this string.
* @param other the string argument.
* @param ooffset the starting offset of the sub-region in the string argument.
* @param len the number of characters to compare.
* @return <code>true</code> if the specified sub-region of this string
* exactly matches the specified sub-region of the string argument;
* <code>false</code> otherwise.
*/
public static boolean regionMatches(StringBuffer str, int toffset, String other, int ooffset, int len) {
int to = toffset;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
int count = str.length();
int otherCount = other.length();
if ((ooffset < 0) || (toffset < 0) || (toffset > (long)count - len) || (ooffset > (long)otherCount - len)) {
return false;
}
while (len-- > 0) {
if (str.charAt(to++) != other.charAt(po++)) {
return false;
}
}
return true;
}
/**
* Converts source to be suitable for use as an identifier in JavaScript. prefix is prefixed to source
* if the first character of source is not suitable to start an identifier (e.g. a number). Any characters
* in source that are not allowed in an identifier are replaced with replacement.
*
* @see Character#isJavaIdentifierStart(char)
* @see Character#isJavaIdentifierPart(char)
*
* @param source String to make into a identifier name
* @param prefix String to prefix source with to make it a valid identifier name
* @param replacement character to use to replace characters in source that are no allowed in an identifier name
* @return source converted to a name suitable for use as an identifier in JavaScript
*/
public static String safeIdentifierName(String source, String prefix, char replacement)
{
StringBuilder b = new StringBuilder();
// Add prefix if source does not start with valid character
if (source == null || source.length() == 0 || !Character.isJavaIdentifierStart(source.charAt(0))) {
b.append(prefix);
}
b.append(source);
for (int i = 0; i < b.length(); i++) {
char c = b.charAt(i);
if ( ! Character.isJavaIdentifierPart(c)) {
b.setCharAt(i, replacement);
}
}
return b.toString();
}
/**
* Convenience method to call safeIdentifierName(source, prefix, '_')
*
* @see #safeIdentifierName(String, String, char)
*
* @param source String to make into a identifier name
* @param prefix String to prefix source with to make it a valid identifier name
* @return source converted to a name suitable for use as an identifier in JavaScript
*/
public static String safeIdentifierName(String source, String prefix) {
return safeIdentifierName(source, prefix, '_');
}
/**
* Convenience method to call safeIdentifierName(source, "_", '_')
*
* @see #safeIdentifierName(String, String, char)
*
* @param source String to make into a identifier name
* @return source converted to a name suitable for use as an identifier in JavaScript
*/
public static String safeIdentifierName(String source) {
return safeIdentifierName(source, "_", '_');
}
/**
* Utility to encode an URL without the try/catch. Throws an NSForwardException in the unlikely case that ERXMessageEncoding.defaultEncoding() can't be found.
* @param string
*/
public static String urlEncode(String string) {
try {
return URLEncoder.encode(string, ERXMessageEncoding.defaultEncoding());
}
catch (UnsupportedEncodingException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
/**
* Utility to decode an URL without the try/catch. Throws an NSForwardException in the unlikely case that ERXMessageEncoding.defaultEncoding() can't be found.
* @param string
*/
public static String urlDecode(String string) {
try {
return URLDecoder.decode(string, ERXMessageEncoding.defaultEncoding());
}
catch (UnsupportedEncodingException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
/**
* Utility to convert to UTF-8 bytes without the try/catch. Throws an NSForwardException in the unlikely case that your encoding can't be found.
* @param string string to convert
*/
public static byte[] toUTF8Bytes(String string) {
return toBytes(string, CharEncoding.UTF_8);
}
/**
* Utility to convert to bytes without the try/catch. Throws an NSForwardException in the unlikely case that your encoding can't be found.
* @param string string to convert
* @param encoding
*/
public static byte[] toBytes(String string, String encoding) {
if(string == null) {
return null;
}
try {
return string.getBytes(encoding);
}
catch (UnsupportedEncodingException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
/**
* Utility to convert from UTF-8 bytes without the try/catch. Throws an NSForwardException in the unlikely case that your encoding can't be found.
* @param bytes string to convert
*/
public static String fromUTF8Bytes(byte bytes[]) {
return fromBytes(bytes, CharEncoding.UTF_8);
}
/**
* Utility to convert from bytes without the try/catch. Throws an NSForwardException in the unlikely case that your encoding can't be found.
* @param bytes string to convert
* @param encoding
*/
public static String fromBytes(byte bytes[], String encoding) {
if(bytes == null) {
return null;
}
try {
return new String(bytes, encoding);
}
catch (UnsupportedEncodingException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
/**
* Pads a string to the specified number of chars by adding the the given pad char on the right side. If the
* string is longer than paddedLength, it is returned unchanged.
*
* @param string the string to pad
* @param padChar the character to pad with
* @param paddedLength the length to pad to
* @return the padded string
*/
public static String rightPad(String string, char padChar, int paddedLength) {
if (string.length() >= paddedLength) {
return string;
}
StringBuilder buffer = new StringBuilder(string);
for (int i = string.length(); i < paddedLength; i++) {
buffer.append(padChar);
}
return buffer.toString();
}
/**
* Pads a string to the specified number of chars by adding the the given pad char on the left side. If the
* string is longer than paddedLength, it is returned unchanged.
*
* @param string the string to pad
* @param padChar the character to pad with
* @param paddedLength the length to pad to
* @return the padded string
*/
public static String leftPad(String string, char padChar, int paddedLength) {
if (string.length() >= paddedLength) {
return string;
}
StringBuilder buffer = new StringBuilder();
for (int i = string.length(); i < paddedLength; i++) {
buffer.append(padChar);
}
buffer.append(string);
return buffer.toString();
}
/**
* Inserts the a string into another string at a particular offset.
*
* @param destinationString the string to insert into
* @param contentToInsert the string to insert
* @param insertOffset the offset in destinationString to insert
* @return the resulting string
*/
public static String insertString(String destinationString, String contentToInsert, int insertOffset) {
String result;
if (destinationString == null) {
if (insertOffset > 0) {
throw new IndexOutOfBoundsException("You attempted to insert '" + contentToInsert + "' into an empty string at the offset " + insertOffset + ".");
}
result = contentToInsert;
}
else {
StringBuilder sb = new StringBuilder(destinationString.length() + contentToInsert.length());
sb.append(destinationString.substring(0, insertOffset));
sb.append(contentToInsert);
sb.append(destinationString.substring(insertOffset));
result = sb.toString();
}
return result;
}
/**
* Null-safe wrapper for java.lang.String.trim
* @param s string to trim
* @return trimmed string or null if s was null
*/
public static String trimString(String s) {
if (s == null) {
return s;
}
return s.trim();
}
/**
* Removes line breaks and quotes the string if necessary
*
* @param s
*
* @return the string in Excel save CSV format
*/
public static String excelSafeCsvString(String s) {
if (s != null) {
boolean mustQuote = false;
s = unquote(s, "\"");
s = s.replaceAll("\r", "");
s = s.replaceAll("\n", "");
if (s.contains("\"")) {
s = s.replaceAll("\"", "\"\"");
mustQuote = true;
}
if (s.contains(","))
mustQuote = true;
if (mustQuote)
s = quote(s, "\"");
}
return s;
}
/**
* Remove the quote symbols from the given string
*
* @param s
* @param quoteSymbol
*
* @return the string unquoted
*/
public static String unquote(String s, String quoteSymbol) {
if (s == null || quoteSymbol == null)
throw new IllegalArgumentException("Neither the string nor the quote symbol are allowed to be null");
if (s.startsWith(quoteSymbol) && s.endsWith(quoteSymbol)) {
s = s.substring(1);
s = s.substring(0, s.length() - 1);
}
return s;
}
/**
* Quote the given string with the provided quote symbols
*
* @param s the string to quote
* @param quoteSymbol - the quote symbol
*
* @return quoted string
*/
public static String quote(String s, String quoteSymbol) {
if (s == null || quoteSymbol == null) {
throw new IllegalArgumentException("Neither the string nor the quote symbol are allowed to be null");
}
s = new StringBuilder().append(quoteSymbol).append(s).append(quoteSymbol).toString();
return s;
}
/**
* Appends a CSS class to an existing (possibly null) CSS class string.
*
* @param originalString the original string
* @param cssClass the new CSS class to append
* @return the CSS classes appended together (with a space between if originalString is non-empty)
*/
public static String stringByAppendingCSSClass(String originalString, String cssClass) {
String newString;
if (cssClass == null || cssClass.length() == 0) {
newString = originalString;
}
else if (originalString == null || originalString.length() == 0) {
newString = cssClass;
}
else {
newString = originalString + " " + cssClass;
}
return newString;
}
/**
* Removes HTML characters from the given string.
*
* @param str the string to remove HTML from
* @param convertChars set to true if you want html special chars to be converted ( ex. © to (C) ), false otherwise
* @return the string without HTML characters in it
*/
public static String stripHtml(String str, boolean convertChars) {
String stripped = str;
if (stripped != null) {
stripped = stripped.replaceAll("<[^>]*>", " ");
if(convertChars) {
stripped = stripped.replaceAll("\\s+", " ");
stripped = stripped.replaceAll("’", "'");
stripped = stripped.replaceAll("©", "(C)");
stripped = stripped.replaceAll("×", " x ");
stripped = stripped.replaceAll("…", "...");
stripped = stripped.replaceAll("—", " -- ");
stripped = stripped.replaceAll("–", " - ");
stripped = stripped.replaceAll("“", "\"");
stripped = stripped.replaceAll("”", "\"");
stripped = stripped.replaceAll("®", "(C)");
stripped = stripped.replaceAll("®", "(R)");
stripped = stripped.replaceAll("™", "(TM)");
stripped = stripped.trim();
}
}
return stripped;
}
/**
* Removes all of the HTML tags from a given string.
* Note: this is a very simplistic implementation
* and will most likely not work with complex HTML.
* Note: for actual conversion of HTML tags into regular
* strings have a look at {@link ERXSimpleHTMLFormatter}
* @param s html string
* @return string with all of its html tags removed
*/
// FIXME: this is so simplistic it will break if you sneeze
public static String removeHTMLTagsFromString(String s) {
StringBuilder result = new StringBuilder();
if (s != null && s.length()>0) {
int position=0;
while (position<s.length()) {
int indexOfOpeningTag=s.indexOf("<",position);
if (indexOfOpeningTag!=-1) {
if (indexOfOpeningTag!=position)
result.append(s.substring(position, indexOfOpeningTag));
position=indexOfOpeningTag+1;
} else {
result.append(s.substring(position, s.length()));
position=s.length();
}
int indexOfClosingTag=s.indexOf(">",position);
if (indexOfClosingTag!=-1) {
position= indexOfClosingTag +1;
} else {
result.append(s.substring(position, s.length()));
position=s.length();
}
}
}
return StringUtils.replace(result.toString(), " "," ");
}
/**
* Returns the value stripped from HTML tags if <b>escapeHTML</b> is false.
* This makes sense because it is not terribly useful to have half-finished tags in your code.
* Note that the "length" of the resulting string is not very exact.
* FIXME: we could remove extra whitespace and character entities here
* @return value stripped from tags.
*/
public static String strippedValue(String value, int length) {
if(value == null || value.length() < 1)
return null;
StringTokenizer tokenizer = new StringTokenizer(value, "<", false);
int token = value.charAt(0) == '<' ? 0 : 1;
String nextPart = null;
StringBuilder result = new StringBuilder();
int currentLength = result.length();
while (tokenizer.hasMoreTokens() && currentLength < length && currentLength < value.length()) {
if(token == 0)
nextPart = tokenizer.nextToken(">");
else {
nextPart = tokenizer.nextToken("<");
if(nextPart.length() > 0 && nextPart.charAt(0) == '>')
nextPart = nextPart.substring(1);
}
if (nextPart != null && token != 0) {
result.append(nextPart);
currentLength += nextPart.length();
}
token = 1 - token;
}
return result.toString();
}
/**
* Attempts to convert string values for attributes into the appropriate
* value class for the attribute. If the method is unable to convert the
* value, it returns null.
*
* @param attr The attribute for the value in question.
* @param strVal The string value to be coerced.
* @param encoding The encoding used if the attribute value class is custom
* and the factory method does not accept a string.
* @param formatter The formatter used if the value class is NSTimestamp.
* @return The coerced object value or null.
*/
public static Object attributeValueFromString(EOAttribute attr, String strVal, String encoding, Format formatter) {
Object val = null;
Class attrValueClass = null;
try {
attrValueClass = Class.forName(attr.className());
} catch (ClassNotFoundException cnfe) {
//An attribute has a className that is not in the classpath
throw NSForwardException._runtimeExceptionForThrowable(cnfe);
}
// If value is a date, parse using the formatter.
if(NSTimestamp.class.equals(attrValueClass)) {
Date parseResult = null;
try {
parseResult = (Date)formatter.parseObject(strVal);
val = new NSTimestamp(parseResult);
} catch(ParseException pe) {
// If the user mangles the date format in the URL, we probably
// want to feed them an error page rather than handle it here.
throw NSForwardException._runtimeExceptionForThrowable(pe);
}
// If number, convert string to number type with reflection.
} else if(Number.class.isAssignableFrom(attrValueClass)) {
val = attributeNumberValueFromString(attr, strVal);
// If string, it's a direct assignment
} else if (String.class.equals(attrValueClass)) {
val = strVal;
// If none of the above, check for a custom factory method.
} else if(attr.valueFactoryMethod()!=null) {
val = attributeCustomValueFromString(attr, strVal, encoding);
}
return val;
}
/**
* Attempts to convert string values for attributes into the appropriate
* value class for the attribute. If the method is unable to convert the
* value, it returns null.
*
* @param attr The attribute for the value in question.
* @param strVal The string value to be coerced.
* @return The coerced object value or null.
*/
public static Number attributeNumberValueFromString(EOAttribute attr, String strVal) {
Number val = null;
// Determine the date class required
String typeString = attr.valueType();
if (typeString != null) {
char key = typeString.charAt(0);
String numberType = null;
switch (key) {
case EOAttribute._VTByte:
numberType = Byte.class.getName();
break;
case EOAttribute._VTShort:
numberType = Short.class.getName();
break;
case EOAttribute._VTInteger:
numberType = Integer.class.getName();
break;
case EOAttribute._VTLong:
numberType = Long.class.getName();
break;
case EOAttribute._VTFloat:
numberType = Float.class.getName();
break;
case EOAttribute._VTDouble:
numberType = Double.class.getName();
break;
case EOAttribute._VTBigDecimal:
numberType = BigDecimal.class.getName();
break;
case EOAttribute._VTBoolean:
numberType = Boolean.class.getName();
break;
default:
break;
}
// Generate value through reflection
if(numberType!=null) {
try {
Class numberClass = Class.forName(numberType);
Constructor numberConstructor = numberClass.getConstructor(new Class[] {String.class});
val = (Number)numberConstructor.newInstance(strVal);
} catch(Exception e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
}
return val;
}
/**
* Attempts to convert string values for attributes into the appropriate
* value class for the attribute. If the method is unable to convert the
* value, it returns null.
*
* @param attr The attribute for the value in question.
* @param strVal The string value to be coerced.
* @param encoding The encoding used if the attribute value class is custom
* and the factory method does not accept a string.
* @return The coerced object value or null.
*/
public static Object attributeCustomValueFromString(EOAttribute attr, String strVal, String encoding) {
Object val = null;
Class attrValueClass = null;
try {
attrValueClass = Class.forName(attr.className());
} catch (ClassNotFoundException cnfe) {
//An attribute has a className that is not in the classpath
NSForwardException._runtimeExceptionForThrowable(cnfe);
}
NSSelector sel = attr.valueFactoryMethod();
try {
Method m = sel.methodOnClass(attrValueClass);
switch (attr.factoryMethodArgumentType()) {
case EOAttribute.FactoryMethodArgumentIsBytes:
if(encoding==null){throw new NullPointerException();}
byte[] b = strVal.getBytes(encoding);
val = m.invoke(null, new Object[] {b});
break;
case EOAttribute.FactoryMethodArgumentIsData:
if(encoding==null){throw new NullPointerException();}
NSData d = new NSData(strVal, encoding);
val = m.invoke(null, new Object[] {d});
break;
case EOAttribute.FactoryMethodArgumentIsString:
val = m.invoke(null, new Object[] {strVal});
break;
default:
break;
}
} catch (NullPointerException npe) {
throw npe;
} catch (Exception e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
return val;
}
/**
* Returns whether the given value falls in a range defined by the given string, which is
* in the format "1-5,100,500,800-1000".
*
* @param value the value to check for
* @param rangeString the range string to parse
* @return whether or not the value falls within the given ranges
*/
public static boolean isValueInRange(int value, String rangeString) {
boolean rangeMatches = false;
if (rangeString != null && rangeString.length() > 0) {
String[] ranges = rangeString.split(",");
for (String range : ranges) {
range = range.trim();
int dashIndex = range.indexOf('-');
if (dashIndex == -1) {
int singleValue = Integer.parseInt(range);
if (value == singleValue) {
rangeMatches = true;
break;
}
}
else {
int lowValue = Integer.parseInt(range.substring(0, dashIndex).trim());
int highValue = Integer.parseInt(range.substring(dashIndex + 1).trim());
if (value >= lowValue && value <= highValue) {
rangeMatches = true;
break;
}
}
}
}
return rangeMatches;
}
/**
* Masks a given string with a single character in the substring specified by the
* begin and end indexes. Negative indexes count from the end of the string
* beginning with -1. For example,
* <code>maskStringWithCharacter("Visa 4111111111111111", '*', 5, -4);</code> will
* result in a string value of "Visa ************1111"
*
* @param arg The string value to mask
* @param mask The character mask
* @param beginIndex The string index where masking begins.
* Negative numbers count down from the end of the string.
* @param endIndex The index where masking ends.
* Negative numbers count down from the end of the string
* @return The masked string result
*/
public static String maskStringWithCharacter(String arg, char mask, int beginIndex, int endIndex) {
int length = arg.length();
//Get the actual begin and end index.
int begin = (beginIndex < 0)?length + beginIndex:beginIndex;
int end = (endIndex < 0)?length + endIndex:endIndex;
int sub = end - begin;
if(sub < 0) {
throw new StringIndexOutOfBoundsException(sub);
}
StringBuilder sb = new StringBuilder(arg.substring(0, begin));
for(int i = 0; i < sub; i++) {
sb.append(mask);
}
sb.append(arg.substring(end, length));
return sb.toString();
}
/**
* A fast Luhn algorithm check. This method only verifies that the string
* argument validates with the Luhn algorithm. It does not attempt to verify
* if the number conforms to ISO/IEC 7812 specifications.
*
* @param value
* A string value consisting of numeric digits between 0-9. If
* the number contains hyphens, spaces, or anything other than a
* string of digits between 0-9 the method returns false.
*
* @return true if the value passes a luhn check, false otherwise.
*/
public static boolean luhnCheck(String value) {
final int length = value.length(), parity = length % 2;
final char zero = '0';
int sum = 0;
for (int i = 0, tmp = 0; i < length; i++) {
tmp = value.charAt(i) - zero;
if (tmp < 0 || tmp > 9) { return false; }
sum += (i % 2 == parity) ? ((2 * tmp) / 10) + ((2 * tmp) % 10) : tmp;
}
return sum % 10 == 0;
}
/**
* Returns a string trimmed about at the max length you define without truncating the last word and adding "..." (if necessary)
*
* @param trimmingString the string you would like to trim
* @param maxLenght the max length you need
* @return the string trimmed
*/
public static String wordSafeTrimmedString(String trimmingString, int maxLenght) {
String cuttedString = trimmingString;
if ( ( trimmingString != null ) && ( trimmingString.length() > maxLenght ) ) {
trimmingString = stripHtml(trimmingString,false);
if( trimmingString.length() > maxLenght) {
int space = trimmingString.indexOf(" ",(maxLenght - 20));
try {
cuttedString = trimmingString.substring(0, space)+" ...";
} catch ( Exception e ) {
//GIVE UP
}
}
}
return cuttedString;
}
/**
* <span class="en">
* trim leading 0 from a (Number) String
*
* @param str - the String
*
* @return Result String
* </span>
*
* <span class="ja">
* 半角数字の前の0を取る処理。
*
* @param str - 処理対象の文字列
*
* @return 処理済みの文字列
* </span>
*/
@SuppressWarnings("javadoc")
public static String trimZeroInFrontOfNumbers(String str){
// 文字有無確認
if(stringIsNullOrEmpty(str))
return str;
// 不必要番号削除処理
int loopIdxMax = str.length() -1;
StringBuilder retStr = new StringBuilder(loopIdxMax +1);
char targetChar;
boolean alladdFlg = false;
for(int loopIdx = 0; loopIdx < loopIdxMax; loopIdx++){
targetChar = str.charAt(loopIdx);
if(alladdFlg || ((targetChar >= '1') && (targetChar <= '9')) ){
retStr.append ( targetChar ); // 文字コードが0以外なら残りを全て保存
alladdFlg = true;
}
}
retStr.append (str.charAt(loopIdxMax)); // 最後のコードを追加
return retStr.toString();
}
/**
* Given an initial string and an array of substrings,
* Removes any occurrences of any of the substrings
* from the initial string. Used in conjunction with
* fuzzy matching.
* @param newString initial string from which to remove other strings
* @param toBeCleaneds array of substrings to be removed from the initial string.
* @return cleaned string.
*/
// FIXME: Should use a StringBuffer instead of creating strings all over the place.
public static String cleanString(String newString, NSArray<String> toBeCleaneds) {
String result=newString;
if (newString!=null) {
for(Enumeration e = toBeCleaneds.objectEnumerator(); e.hasMoreElements();){
String toBeCleaned = (String)e.nextElement();
if(newString.toUpperCase().indexOf(toBeCleaned)>-1){
result=newString.substring(0, newString.toUpperCase().indexOf(toBeCleaned));
}
}
}
return result;
}
public static boolean isBlank(String value) {
boolean isBlank = false;
if (value == null || value.trim().length() == 0) {
isBlank = true;
}
return isBlank;
}
public static boolean isNotBlank(String value) {
return ! isBlank(value);
}
}