org.opencms.util.CmsStringUtil Maven / Gradle / Ivy
Show all versions of opencms-test Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software GmbH & Co. KG, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.util;
import org.opencms.file.CmsResource;
import org.opencms.i18n.CmsEncoder;
import org.opencms.i18n.I_CmsMessageBundle;
import org.opencms.json.JSONException;
import org.opencms.json.JSONObject;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import java.awt.Color;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.logging.Log;
import org.apache.oro.text.perl.MalformedPerl5PatternException;
import org.apache.oro.text.perl.Perl5Util;
import com.cybozu.labs.langdetect.Detector;
import com.cybozu.labs.langdetect.DetectorFactory;
import com.cybozu.labs.langdetect.LangDetectException;
import com.google.common.base.Optional;
/**
* Provides String utility functions.
*
* @since 6.0.0
*/
public final class CmsStringUtil {
/**
* Compares two Strings according to the count of containing slashes.
*
* If both Strings contain the same count of slashes the Strings are compared.
*/
public static class CmsSlashComparator implements Comparator {
/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(String a, String b) {
int slashCountA = countChar(a, '/');
int slashCountB = countChar(b, '/');
if (slashCountA < slashCountB) {
return 1;
} else if (slashCountA == slashCountB) {
return a.compareTo(b);
} else {
return -1;
}
}
}
/** Regular expression that matches the HTML body end tag. */
public static final String BODY_END_REGEX = "<\\s*/\\s*body[^>]*>";
/** Regular expression that matches the HTML body start tag. */
public static final String BODY_START_REGEX = "<\\s*body[^>]*>";
/** Constant for "false"
. */
public static final String FALSE = Boolean.toString(false);
/** a convenient shorthand to the line separator constant. */
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
/** Context macro. */
public static final String MACRO_OPENCMS_CONTEXT = "${OpenCmsContext}";
/** Pattern to determine a locale for suffixes like '_de' or '_en_US'. */
public static final Pattern PATTERN_LOCALE_SUFFIX = Pattern.compile(
"(.*)_([a-z]{2}(?:_[A-Z]{2})?)(?:\\.[^\\.]*)?$");
/** Pattern to determine the document number for suffixes like '_0001'. */
public static final Pattern PATTERN_NUMBER_SUFFIX = Pattern.compile("(.*)_(\\d+)(\\.[^\\.^\\n]*)?$");
/** Pattern matching one or more slashes. */
public static final Pattern PATTERN_SLASHES = Pattern.compile("/+");
/** The place holder end sign in the pattern. */
public static final String PLACEHOLDER_END = "}";
/** The place holder start sign in the pattern. */
public static final String PLACEHOLDER_START = "{";
/** Contains all chars that end a sentence in the {@link #trimToSize(String, int, int, String)} method. */
public static final char[] SENTENCE_ENDING_CHARS = {'.', '!', '?'};
/** a convenient shorthand for tabulations. */
public static final String TABULATOR = " ";
/** Constant for "true"
. */
public static final String TRUE = Boolean.toString(true);
/** Regex pattern that matches an end body tag. */
private static final Pattern BODY_END_PATTERN = Pattern.compile(BODY_END_REGEX, Pattern.CASE_INSENSITIVE);
/** Regex pattern that matches a start body tag. */
private static final Pattern BODY_START_PATTERN = Pattern.compile(BODY_START_REGEX, Pattern.CASE_INSENSITIVE);
/** Day constant. */
private static final long DAYS = 1000 * 60 * 60 * 24;
/** Hour constant. */
private static final long HOURS = 1000 * 60 * 60;
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsStringUtil.class);
/** OpenCms context replace String, static for performance reasons. */
private static String m_contextReplace;
/** OpenCms context search String, static for performance reasons. */
private static String m_contextSearch;
/** Minute constant. */
private static final long MINUTES = 1000 * 60;
/** Second constant. */
private static final long SECONDS = 1000;
/** Regex that matches an encoding String in an xml head. */
private static final Pattern XML_ENCODING_REGEX = Pattern.compile(
"encoding\\s*=\\s*[\"'].+[\"']",
Pattern.CASE_INSENSITIVE);
/** Regex that matches an xml head. */
private static final Pattern XML_HEAD_REGEX = Pattern.compile("<\\s*\\?.*\\?\\s*>", Pattern.CASE_INSENSITIVE);
/** Units used for duration parsing. */
private static final String[] DURATION_UNTIS = {"d", "h", "m", "s", "ms"};
/** Multipliers used for duration parsing. */
private static final long[] DURATION_MULTIPLIERS = {24L * 60 * 60 * 1000, 60L * 60 * 1000, 60L * 1000, 1000L, 1L};
/** Number and unit pattern for duration parsing. */
private static final Pattern DURATION_NUMBER_AND_UNIT_PATTERN = Pattern.compile("([0-9]+)([a-z]+)");
/**
* Default constructor (empty), private because this class has only
* static methods.
*/
private CmsStringUtil() {
// empty
}
/**
* Adds leading and trailing slashes to a path,
* if the path does not already start or end with a slash.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param path the path to which add the slashes
*
* @return the path with added leading and trailing slashes
*/
public static String addLeadingAndTrailingSlash(String path) {
StringBuffer buffer1 = new StringBuffer();
if (!path.startsWith("/")) {
buffer1.append("/");
}
buffer1.append(path);
if (!path.endsWith("/")) {
buffer1.append("/");
}
return buffer1.toString();
}
/**
* Returns a string representation for the given array using the given separator.
*
* @param arg the array to transform to a String
* @param separator the item separator
*
* @return the String of the given array
*/
public static String arrayAsString(final String[] arg, String separator) {
StringBuffer result = new StringBuffer();
for (int i = 0; i < arg.length; i++) {
result.append(arg[i]);
if ((i + 1) < arg.length) {
result.append(separator);
}
}
return result.toString();
}
/**
* Changes the given filenames suffix from the current suffix to the provided suffix.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param filename the filename to be changed
* @param suffix the new suffix of the file
*
* @return the filename with the replaced suffix
*/
public static String changeFileNameSuffixTo(String filename, String suffix) {
int dotPos = filename.lastIndexOf('.');
if (dotPos != -1) {
return filename.substring(0, dotPos + 1) + suffix;
} else {
// the string has no suffix
return filename;
}
}
/**
* Checks if a given name is composed only of the characters a...z,A...Z,0...9
* and the provided constraints
.
*
* If the check fails, an Exception is generated. The provided bundle and key is
* used to generate the Exception. 4 parameters are passed to the Exception:
* - The
name
* - The first illegal character found
*
- The position where the illegal character was found
*
- The
constraints
*
* @param name the name to check
* @param constraints the additional character constraints
* @param key the key to use for generating the Exception (if required)
* @param bundle the bundle to use for generating the Exception (if required)
*
* @throws CmsIllegalArgumentException if the check fails (generated from the given key and bundle)
*/
public static void checkName(String name, String constraints, String key, I_CmsMessageBundle bundle)
throws CmsIllegalArgumentException {
int l = name.length();
for (int i = 0; i < l; i++) {
char c = name.charAt(i);
if (((c < 'a') || (c > 'z'))
&& ((c < '0') || (c > '9'))
&& ((c < 'A') || (c > 'Z'))
&& (constraints.indexOf(c) < 0)) {
throw new CmsIllegalArgumentException(
bundle.container(key, new Object[] {name, new Character(c), new Integer(i), constraints}));
}
}
}
/**
* Returns a string representation for the given collection using the given separator.
*
* @param collection the collection to print
* @param separator the item separator
*
* @return the string representation for the given collection
*/
public static String collectionAsString(Collection> collection, String separator) {
StringBuffer string = new StringBuffer(128);
Iterator> it = collection.iterator();
while (it.hasNext()) {
string.append(it.next());
if (it.hasNext()) {
string.append(separator);
}
}
return string.toString();
}
/**
* Compares two paths, ignoring leading and trailing slashes.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param path1 the first path
* @param path2 the second path
*
* @return true if the paths are equal (ignoring leading and trailing slashes)
*/
public static boolean comparePaths(String path1, String path2) {
return addLeadingAndTrailingSlash(path1).equals(addLeadingAndTrailingSlash(path2));
}
/**
* Counts the occurrence of a given char in a given String.
*
* @param s the string
* @param c the char to count
*
* @return returns the count of occurrences of a given char in a given String
*/
public static int countChar(String s, char c) {
int counter = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == c) {
counter++;
}
}
return counter;
}
/**
* Returns a String array representation for the given enum.
*
* @param the type of the enum
* @param values the enum values
*
* @return the representing String array
*/
public static > String[] enumNameToStringArray(T[] values) {
int i = 0;
String[] result = new String[values.length];
for (T value : values) {
result[i++] = value.name();
}
return result;
}
/**
* Replaces line breaks to <br/>
and HTML control characters
* like < > & "
with their HTML entity representation.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param source the String to escape
*
* @return the escaped String
*/
public static String escapeHtml(String source) {
if (source == null) {
return null;
}
source = CmsEncoder.escapeXml(source);
source = CmsStringUtil.substitute(source, "\r", "");
source = CmsStringUtil.substitute(source, "\n", "
\n");
return source;
}
/**
* Escapes a String so it may be used in JavaScript String definitions.
*
* This method escapes
* line breaks (\r\n,\n
) quotation marks (".'
)
* and slash as well as backspace characters (\,/
).
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param source the String to escape
*
* @return the escaped String
*/
public static String escapeJavaScript(String source) {
source = CmsStringUtil.substitute(source, "\\", "\\\\");
source = CmsStringUtil.substitute(source, "\"", "\\\"");
source = CmsStringUtil.substitute(source, "\'", "\\\'");
source = CmsStringUtil.substitute(source, "\r\n", "\\n");
source = CmsStringUtil.substitute(source, "\n", "\\n");
// to avoid XSS (closing script tags) in embedded Javascript
source = CmsStringUtil.substitute(source, "/", "\\/");
return source;
}
/**
* Escapes a String so it may be used as a Perl5 regular expression.
*
* This method replaces the following characters in a String:
* {}[]()\$^.*+/
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param source the string to escape
*
* @return the escaped string
*/
public static String escapePattern(String source) {
if (source == null) {
return null;
}
StringBuffer result = new StringBuffer(source.length() * 2);
for (int i = 0; i < source.length(); ++i) {
char ch = source.charAt(i);
switch (ch) {
case '\\':
result.append("\\\\");
break;
case '/':
result.append("\\/");
break;
case '$':
result.append("\\$");
break;
case '^':
result.append("\\^");
break;
case '.':
result.append("\\.");
break;
case '*':
result.append("\\*");
break;
case '+':
result.append("\\+");
break;
case '|':
result.append("\\|");
break;
case '?':
result.append("\\?");
break;
case '{':
result.append("\\{");
break;
case '}':
result.append("\\}");
break;
case '[':
result.append("\\[");
break;
case ']':
result.append("\\]");
break;
case '(':
result.append("\\(");
break;
case ')':
result.append("\\)");
break;
default:
result.append(ch);
}
}
return new String(result);
}
/**
* This method takes a part of a html tag definition, an attribute to extend within the
* given text and a default value for this attribute; and returns a {@link Map}
* with 2 values: a {@link String}
with key "text"
with the new text
* without the given attribute, and another {@link String}
with key "value"
* with the new extended value for the given attribute, this value is surrounded by the same type of
* quotation marks as in the given text.
*
* @param text the text to search in
* @param attribute the attribute to remove and extend from the text
* @param defValue a default value for the attribute, should not have any quotation mark
*
* @return a map with the new text and the new value for the given attribute
*/
public static Map extendAttribute(String text, String attribute, String defValue) {
Map retValue = new HashMap();
retValue.put("text", text);
retValue.put("value", "'" + defValue + "'");
if ((text != null) && (text.toLowerCase().indexOf(attribute.toLowerCase()) >= 0)) {
// this does not work for things like "att=method()" without quotations.
String quotation = "\'";
int pos1 = text.toLowerCase().indexOf(attribute.toLowerCase());
// looking for the opening quotation mark
int pos2 = text.indexOf(quotation, pos1);
int test = text.indexOf("\"", pos1);
if ((test > -1) && ((pos2 == -1) || (test < pos2))) {
quotation = "\"";
pos2 = test;
}
// assuming there is a closing quotation mark
int pos3 = text.indexOf(quotation, pos2 + 1);
// building the new attribute value
String newValue = quotation + defValue + text.substring(pos2 + 1, pos3 + 1);
// removing the onload statement from the parameters
String newText = text.substring(0, pos1);
if (pos3 < text.length()) {
newText += text.substring(pos3 + 1);
}
retValue.put("text", newText);
retValue.put("value", newValue);
}
return retValue;
}
/**
* Extracts the content of a <body>
tag in a HTML page.
*
* This method should be pretty robust and work even if the input HTML does not contains
* a valid body tag.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param content the content to extract the body from
*
* @return the extracted body tag content
*/
public static String extractHtmlBody(String content) {
Matcher startMatcher = BODY_START_PATTERN.matcher(content);
Matcher endMatcher = BODY_END_PATTERN.matcher(content);
int start = 0;
int end = content.length();
if (startMatcher.find()) {
start = startMatcher.end();
}
if (endMatcher.find(start)) {
end = endMatcher.start();
}
return content.substring(start, end);
}
/**
* Extracts the xml encoding setting from an xml file that is contained in a String by parsing
* the xml head.
*
* This is useful if you have a byte array that contains a xml String,
* but you do not know the xml encoding setting. Since the encoding setting
* in the xml head is usually encoded with standard US-ASCII, you usually
* just create a String of the byte array without encoding setting,
* and use this method to find the 'true' encoding. Then create a String
* of the byte array again, this time using the found encoding.
*
* This method will return null
in case no xml head
* or encoding information is contained in the input.
*
* @param content the xml content to extract the encoding from
*
* @return the extracted encoding, or null if no xml encoding setting was found in the input
*/
public static String extractXmlEncoding(String content) {
String result = null;
Matcher xmlHeadMatcher = XML_HEAD_REGEX.matcher(content);
if (xmlHeadMatcher.find()) {
String xmlHead = xmlHeadMatcher.group();
Matcher encodingMatcher = XML_ENCODING_REGEX.matcher(xmlHead);
if (encodingMatcher.find()) {
String encoding = encodingMatcher.group();
int pos1 = encoding.indexOf('=') + 2;
String charset = encoding.substring(pos1, encoding.length() - 1);
if (Charset.isSupported(charset)) {
result = charset;
}
}
}
return result;
}
/**
* Shortens a resource name or path so that it is not longer than the provided maximum length.
*
* In order to reduce the length of the resource name, only
* complete folder names are removed and replaced with ... successively,
* starting with the second folder.
* The first folder is removed only in case the result still does not fit
* if all subfolders have been removed.
*
* Example: formatResourceName("/myfolder/subfolder/index.html", 21)
* returns /myfolder/.../index.html
.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param name the resource name to format
* @param maxLength the maximum length of the resource name (without leading /...
)
*
* @return the formatted resource name
*/
public static String formatResourceName(String name, int maxLength) {
if (name == null) {
return null;
}
if (name.length() <= maxLength) {
return name;
}
int total = name.length();
String[] names = CmsStringUtil.splitAsArray(name, "/");
if (name.endsWith("/")) {
names[names.length - 1] = names[names.length - 1] + "/";
}
for (int i = 1; (total > maxLength) && (i < (names.length - 1)); i++) {
if (i > 1) {
names[i - 1] = "";
}
names[i] = "...";
total = 0;
for (int j = 0; j < names.length; j++) {
int l = names[j].length();
total += l + ((l > 0) ? 1 : 0);
}
}
if (total > maxLength) {
names[0] = (names.length > 2) ? "" : (names.length > 1) ? "..." : names[0];
}
StringBuffer result = new StringBuffer();
for (int i = 0; i < names.length; i++) {
if (names[i].length() > 0) {
result.append("/");
result.append(names[i]);
}
}
return result.toString();
}
/**
* Formats a runtime in the format hh:mm:ss, to be used e.g. in reports.
*
* If the runtime is greater then 24 hours, the format dd:hh:mm:ss is used.
*
* @param runtime the time to format
*
* @return the formatted runtime
*/
public static String formatRuntime(long runtime) {
long seconds = (runtime / SECONDS) % 60;
long minutes = (runtime / MINUTES) % 60;
long hours = (runtime / HOURS) % 24;
long days = runtime / DAYS;
StringBuffer strBuf = new StringBuffer();
if (days > 0) {
if (days < 10) {
strBuf.append('0');
}
strBuf.append(days);
strBuf.append(':');
}
if (hours < 10) {
strBuf.append('0');
}
strBuf.append(hours);
strBuf.append(':');
if (minutes < 10) {
strBuf.append('0');
}
strBuf.append(minutes);
strBuf.append(':');
if (seconds < 10) {
strBuf.append('0');
}
strBuf.append(seconds);
return strBuf.toString();
}
/**
* Returns the color value ({@link Color}
) for the given String value.
*
* All parse errors are caught and the given default value is returned in this case.
*
* @param value the value to parse as color
* @param defaultValue the default value in case of parsing errors
* @param key a key to be included in the debug output in case of parse errors
*
* @return the int value for the given parameter value String
*/
public static Color getColorValue(String value, Color defaultValue, String key) {
Color result;
try {
char pre = value.charAt(0);
if (pre != '#') {
value = "#" + value;
}
result = Color.decode(value);
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_COLOR_2, value, key));
}
result = defaultValue;
}
return result;
}
/**
* Returns the common parent path of two paths.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param first the first path
* @param second the second path
*
* @return the common prefix path
*/
public static String getCommonPrefixPath(String first, String second) {
List firstComponents = getPathComponents(first);
List secondComponents = getPathComponents(second);
int minSize = Math.min(firstComponents.size(), secondComponents.size());
StringBuffer resultBuffer = new StringBuffer();
for (int i = 0; i < minSize; i++) {
if (firstComponents.get(i).equals(secondComponents.get(i))) {
resultBuffer.append("/");
resultBuffer.append(firstComponents.get(i));
} else {
break;
}
}
String result = resultBuffer.toString();
if (result.length() == 0) {
result = "/";
}
return result;
}
/**
* Returns the Ethernet-Address of the locale host.
*
* A dummy ethernet address is returned, if the ip is
* representing the loopback address or in case of exceptions.
*
* @return the Ethernet-Address
*/
public static String getEthernetAddress() {
try {
InetAddress ip = InetAddress.getLocalHost();
if (!ip.isLoopbackAddress()) {
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
byte[] mac = network.getHardwareAddress();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mac.length; i++) {
sb.append(String.format("%02X%s", new Byte(mac[i]), (i < (mac.length - 1)) ? ":" : ""));
}
return sb.toString();
}
} catch (Throwable t) {
// if an exception occurred return a dummy address
}
// return a dummy ethernet address, if the ip is representing the loopback address or in case of exceptions
return CmsUUID.getDummyEthernetAddress();
}
/**
* Returns the Integer (int) value for the given String value.
*
* All parse errors are caught and the given default value is returned in this case.
*
* @param value the value to parse as int
* @param defaultValue the default value in case of parsing errors
* @param key a key to be included in the debug output in case of parse errors
*
* @return the int value for the given parameter value String
*/
public static int getIntValue(String value, int defaultValue, String key) {
int result;
try {
result = Integer.valueOf(value).intValue();
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_INT_2, value, key));
}
result = defaultValue;
}
return result;
}
/**
* Returns the closest Integer (int) value for the given String value.
*
* All parse errors are caught and the given default value is returned in this case.
*
* @param value the value to parse as int, can also represent a float value
* @param defaultValue the default value in case of parsing errors
* @param key a key to be included in the debug output in case of parse errors
*
* @return the closest int value for the given parameter value String
*/
public static int getIntValueRounded(String value, int defaultValue, String key) {
int result;
try {
result = Math.round(Float.parseFloat(value));
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_INT_2, value, key));
}
result = defaultValue;
}
return result;
}
/**
* Returns a Locale calculated from the suffix of the given String, or null
if no locale suffix is found.
*
* The locale returned will include the optional country code if this was part of the suffix.
*
* Calls {@link CmsResource#getName(String)} first, so the given name can also be a resource root path.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param name the name to get the locale for
*
* @return the locale, or null
*
* @see #getLocaleSuffixForName(String)
*/
public static Locale getLocaleForName(String name) {
String suffix = getLocaleSuffixForName(CmsResource.getName(name));
if (suffix != null) {
String laguageString = suffix.substring(0, 2);
return suffix.length() == 5 ? new Locale(laguageString, suffix.substring(3, 5)) : new Locale(laguageString);
}
return null;
}
/**
* Returns the locale for the given text based on the language detection library.
*
* The result will be null
if the detection fails or the detected locale is not configured
* in the 'opencms-system.xml' as available locale.
*
* @param text the text to retrieve the locale for
*
* @return the detected locale for the given text
*/
public static Locale getLocaleForText(String text) {
// try to detect locale by language detector
if (isNotEmptyOrWhitespaceOnly(text)) {
try {
Detector detector = DetectorFactory.create();
detector.append(text);
String lang = detector.detect();
Locale loc = new Locale(lang);
if (OpenCms.getLocaleManager().getAvailableLocales().contains(loc)) {
return loc;
}
} catch (LangDetectException e) {
LOG.debug(e);
}
}
return null;
}
/**
* Returns the locale suffix from the given String, or null
if no locae suffix is found.
*
* Uses the the {@link #PATTERN_LOCALE_SUFFIX} to find a language_country occurrence in the
* given name and returns the first group of the match.
*
* Examples:
*
*
* rabbit_en_EN.html -> Locale[en_EN]
* rabbit_en_EN -> Locale[en_EN]
* rabbit_en.html -> Locale[en]
* rabbit_en -> Locale[en]
* rabbit_en. -> Locale[en]
* rabbit_enr -> null
* rabbit_en.tar.gz -> null
*
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param name the resource name to get the locale suffix for
*
* @return the locale suffix if found, null
otherwise
*/
public static String getLocaleSuffixForName(String name) {
Matcher matcher = PATTERN_LOCALE_SUFFIX.matcher(name);
if (matcher.find()) {
return matcher.group(2);
}
return null;
}
/**
* Returns the Long (long) value for the given String value.
*
* All parse errors are caught and the given default value is returned in this case.
*
* @param value the value to parse as long
* @param defaultValue the default value in case of parsing errors
* @param key a key to be included in the debug output in case of parse errors
*
* @return the long value for the given parameter value String
*/
public static long getLongValue(String value, long defaultValue, String key) {
long result;
try {
result = Long.valueOf(value).longValue();
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_INT_2, value, key));
}
result = defaultValue;
}
return result;
}
/**
* Splits a path into its non-empty path components.
*
* If the path is the root path, an empty list will be returned.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param path the path to split
*
* @return the list of non-empty path components
*/
public static List getPathComponents(String path) {
List result = CmsStringUtil.splitAsList(path, "/");
Iterator iter = result.iterator();
while (iter.hasNext()) {
String token = iter.next();
if (CmsStringUtil.isEmptyOrWhitespaceOnly(token)) {
iter.remove();
}
}
return result;
}
/**
* Converts the given path to a path relative to a base folder,
* but only if it actually is a sub-path of the latter,
* otherwise null
is returned.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param base the base path
* @param path the path which should be converted to a relative path
*
* @return 'path' converted to a path relative to 'base', or null if 'path' is not a sub-folder of 'base'
*/
public static String getRelativeSubPath(String base, String path) {
String result = null;
base = CmsStringUtil.joinPaths(base, "/");
path = CmsStringUtil.joinPaths(path, "/");
if (path.startsWith(base)) {
result = path.substring(base.length());
}
if (result != null) {
if (result.endsWith("/")) {
result = result.substring(0, result.length() - 1);
}
if (!result.startsWith("/")) {
result = "/" + result;
}
}
return result;
}
/**
* Returns true
if the provided String is either null
* or the empty String ""
.
*
* @param value the value to check
*
* @return true, if the provided value is null or the empty String, false otherwise
*/
public static boolean isEmpty(String value) {
return (value == null) || (value.length() == 0);
}
/**
* Returns true
if the provided String is either null
* or contains only white spaces.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param value the value to check
*
* @return true, if the provided value is null or contains only white spaces, false otherwise
*/
public static boolean isEmptyOrWhitespaceOnly(String value) {
return isEmpty(value) || (value.trim().length() == 0);
}
/**
* Returns true
if the provided Objects are either both null
* or equal according to {@link Object#equals(Object)}.
*
* @param value1 the first object to compare
* @param value2 the second object to compare
*
* @return true
if the provided Objects are either both null
* or equal according to {@link Object#equals(Object)}
*/
public static boolean isEqual(Object value1, Object value2) {
if (value1 == null) {
return (value2 == null);
}
return value1.equals(value2);
}
/**
* Returns true
if the provided String is neither null
* nor the empty String ""
.
*
* @param value the value to check
*
* @return true, if the provided value is not null and not the empty String, false otherwise
*/
public static boolean isNotEmpty(String value) {
return (value != null) && (value.length() != 0);
}
/**
* Returns true
if the provided String is neither null
* nor contains only white spaces.
*
* @param value the value to check
*
* @return true
, if the provided value is null
* or contains only white spaces, false
otherwise
*/
public static boolean isNotEmptyOrWhitespaceOnly(String value) {
return (value != null) && (value.trim().length() > 0);
}
/**
* Checks if the first path is a prefix of the second path.
*
* This method is different compared to {@link String#startsWith},
* because it considers /foo/bar
to
* be a prefix path of /foo/bar/baz
,
* but not of /foo/bar42
.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param firstPath the first path
* @param secondPath the second path
*
* @return true if the first path is a prefix path of the second path
*/
public static boolean isPrefixPath(String firstPath, String secondPath) {
firstPath = CmsStringUtil.joinPaths(firstPath, "/");
secondPath = CmsStringUtil.joinPaths(secondPath, "/");
return secondPath.startsWith(firstPath);
}
/**
* Checks if the given class name is a valid Java class name.
*
* Directly exposed for JSP EL, not through {@link org.opencms.jsp.util.CmsJspElFunctions}.
*
* @param className the name to check
*
* @return true if the given class name is a valid Java class name
*/
public static boolean isValidJavaClassName(String className) {
if (CmsStringUtil.isEmpty(className)) {
return false;
}
int length = className.length();
boolean nodot = true;
for (int i = 0; i < length; i++) {
char ch = className.charAt(i);
if (nodot) {
if (ch == '.') {
return false;
} else if (Character.isJavaIdentifierStart(ch)) {
nodot = false;
} else {
return false;
}
} else {
if (ch == '.') {
nodot = true;
} else if (Character.isJavaIdentifierPart(ch)) {
nodot = false;
} else {
return false;
}
}
}
return true;
}
/**
* Concatenates multiple paths and separates them with '/'.
*
* Consecutive slashes will be reduced to a single slash in the resulting string.
* For example, joinPaths("/foo/", "/bar", "baz") will return "/foo/bar/baz".
*
* @param paths the list of paths
*
* @return the joined path
*/
public static String joinPaths(List paths) {
String result = listAsString(paths, "/");
// result may now contain multiple consecutive slashes, so reduce them to single slashes
result = PATTERN_SLASHES.matcher(result).replaceAll("/");
return result;
}
/**
* Concatenates multiple paths and separates them with '/'.
*
* Consecutive slashes will be reduced to a single slash in the resulting string.
* For example joinPaths("/foo/", "/bar", "baz") will return "/foo/bar/baz".
*
* If one of the argument paths already contains a double "//" this will also be reduced to '/'.
* For example joinPaths("/foo//bar/", "/baz") will return "/foo/bar/baz".
*
* @param paths the array of paths
*
* @return the joined path
*/
public static String joinPaths(String... paths) {
StringBuffer result = new StringBuffer(paths.length * 32);
boolean noSlash = true;
for (int i = 0; i < paths.length; i++) {
for (int j = 0; j < paths[i].length(); j++) {
char c = paths[i].charAt(j);
if (c != '/') {
result.append(c);
noSlash = true;
} else if (noSlash) {
result.append('/');
noSlash = false;
}
}
if (noSlash && (i < (paths.length - 1))) {
result.append('/');
noSlash = false;
}
}
return result.toString();
}
/**
* Returns the last index of any of the given chars in the given source.
*
* If no char is found, -1 is returned.
*
* @param source the source to check
* @param chars the chars to find
*
* @return the last index of any of the given chars in the given source, or -1
*/
public static int lastIndexOf(String source, char[] chars) {
// now try to find an "sentence ending" char in the text in the "findPointArea"
int result = -1;
for (int i = 0; i < chars.length; i++) {
int pos = source.lastIndexOf(chars[i]);
if (pos > result) {
// found new last char
result = pos;
}
}
return result;
}
/**
* Returns the last index a whitespace char the given source.
*
* If no whitespace char is found, -1 is returned.
*
* @param source the source to check
*
* @return the last index a whitespace char the given source, or -1
*/
public static int lastWhitespaceIn(String source) {
if (CmsStringUtil.isEmpty(source)) {
return -1;
}
int pos = -1;
for (int i = source.length() - 1; i >= 0; i--) {
if (Character.isWhitespace(source.charAt(i))) {
pos = i;
break;
}
}
return pos;
}
/**
* Returns a string representation for the given list using the given separator.
*
* @param list the list to write
* @param separator the item separator string
*
* @return the string representation for the given map
*/
public static String listAsString(List> list, String separator) {
StringBuffer string = new StringBuffer(128);
Iterator> it = list.iterator();
while (it.hasNext()) {
string.append(it.next());
if (it.hasNext()) {
string.append(separator);
}
}
return string.toString();
}
/**
* Encodes a map with string keys and values as a JSON string with the same keys/values.
*
* @param map the input map
* @return the JSON data containing the map entries
*/
public static String mapAsJson(Map map) {
JSONObject obj = new JSONObject();
for (Map.Entry entry : map.entrySet()) {
try {
obj.put(entry.getKey(), entry.getValue());
} catch (JSONException e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
return obj.toString();
}
/**
* Returns a string representation for the given map using the given separators.
*
* @param type of map keys
* @param type of map values
* @param map the map to write
* @param sepItem the item separator string
* @param sepKeyval the key-value pair separator string
*
* @return the string representation for the given map
*/
public static String mapAsString(Map map, String sepItem, String sepKeyval) {
StringBuffer string = new StringBuffer(128);
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
string.append(entry.getKey());
string.append(sepKeyval);
string.append(entry.getValue());
if (it.hasNext()) {
string.append(sepItem);
}
}
return string.toString();
}
/**
* Applies white space padding to the left of the given String.
*
* @param input the input to pad left
* @param size the size of the padding
*
* @return the input padded to the left
*/
public static String padLeft(String input, int size) {
return (new PrintfFormat("%" + size + "s")).sprintf(input);
}
/**
* Applies white space padding to the right of the given String.
*
* @param input the input to pad right
* @param size the size of the padding
*
* @return the input padded to the right
*/
public static String padRight(String input, int size) {
return (new PrintfFormat("%-" + size + "s")).sprintf(input);
}
/**
* Parses a duration and returns the corresponding number of milliseconds.
*
* Durations consist of a space-separated list of components of the form {number}{time unit},
* for example 1d 5m. The available units are d (days), h (hours), m (months), s (seconds), ms (milliseconds).
*
* @param durationStr the duration string
* @param defaultValue the default value to return in case the pattern does not match
* @return the corresponding number of milliseconds
*/
public static final long parseDuration(String durationStr, long defaultValue) {
durationStr = durationStr.toLowerCase().trim();
Matcher matcher = DURATION_NUMBER_AND_UNIT_PATTERN.matcher(durationStr);
long millis = 0;
boolean matched = false;
while (matcher.find()) {
long number = Long.valueOf(matcher.group(1)).longValue();
String unit = matcher.group(2);
long multiplier = 0;
for (int j = 0; j < DURATION_UNTIS.length; j++) {
if (unit.equals(DURATION_UNTIS[j])) {
multiplier = DURATION_MULTIPLIERS[j];
break;
}
}
if (multiplier == 0) {
LOG.warn("parseDuration: Unknown unit " + unit);
} else {
matched = true;
}
millis += number * multiplier;
}
if (!matched) {
millis = defaultValue;
}
return millis;
}
/**
* Replaces a constant prefix with another string constant in a given text.
*
* If the input string does not start with the given prefix, Optional.absent() is returned.
*
* @param text the text for which to replace the prefix
* @param origPrefix the original prefix
* @param newPrefix the replacement prefix
* @param ignoreCase if true, upper-/lower case differences will be ignored
*
* @return an Optional containing either the string with the replaced prefix, or an absent value if the prefix could not be replaced
*/
public static Optional replacePrefix(String text, String origPrefix, String newPrefix, boolean ignoreCase) {
String prefixTestString = ignoreCase ? text.toLowerCase() : text;
origPrefix = ignoreCase ? origPrefix.toLowerCase() : origPrefix;
if (prefixTestString.startsWith(origPrefix)) {
return Optional.of(newPrefix + text.substring(origPrefix.length()));
} else {
return Optional.absent();
}
}
/**
* Splits a String into substrings along the provided char delimiter and returns
* the result as an Array of Substrings.
*
* @param source the String to split
* @param delimiter the delimiter to split at
*
* @return the Array of splitted Substrings
*/
public static String[] splitAsArray(String source, char delimiter) {
List result = splitAsList(source, delimiter);
return result.toArray(new String[result.size()]);
}
/**
* Splits a String into substrings along the provided String delimiter and returns
* the result as an Array of Substrings.
*
* @param source the String to split
* @param delimiter the delimiter to split at
*
* @return the Array of splitted Substrings
*/
public static String[] splitAsArray(String source, String delimiter) {
List result = splitAsList(source, delimiter);
return result.toArray(new String[result.size()]);
}
/**
* Splits a String into substrings along the provided char delimiter and returns
* the result as a List of Substrings.
*
* @param source the String to split
* @param delimiter the delimiter to split at
*
* @return the List of splitted Substrings
*/
public static List splitAsList(String source, char delimiter) {
return splitAsList(source, delimiter, false);
}
/**
* Splits a String into substrings along the provided char delimiter and returns
* the result as a List of Substrings.
*
* @param source the String to split
* @param delimiter the delimiter to split at
* @param trim flag to indicate if leading and trailing white spaces should be omitted
*
* @return the List of splitted Substrings
*/
public static List splitAsList(String source, char delimiter, boolean trim) {
List result = new ArrayList();
int i = 0;
int l = source.length();
int n = source.indexOf(delimiter);
while (n != -1) {
// zero - length items are not seen as tokens at start or end
if ((i < n) || ((i > 0) && (i < l))) {
result.add(trim ? source.substring(i, n).trim() : source.substring(i, n));
}
i = n + 1;
n = source.indexOf(delimiter, i);
}
// is there a non - empty String to cut from the tail?
if (n < 0) {
n = source.length();
}
if (i < n) {
result.add(trim ? source.substring(i).trim() : source.substring(i));
}
return result;
}
/**
* Splits a String into substrings along the provided String delimiter and returns
* the result as List of Substrings.
*
* @param source the String to split
* @param delimiter the delimiter to split at
*
* @return the Array of splitted Substrings
*/
public static List splitAsList(String source, String delimiter) {
return splitAsList(source, delimiter, false);
}
/**
* Splits a String into substrings along the provided String delimiter and returns
* the result as List of Substrings.
*
* @param source the String to split
* @param delimiter the delimiter to split at
* @param trim flag to indicate if leading and trailing white spaces should be omitted
*
* @return the Array of splitted Substrings
*/
public static List splitAsList(String source, String delimiter, boolean trim) {
int dl = delimiter.length();
if (dl == 1) {
// optimize for short strings
return splitAsList(source, delimiter.charAt(0), trim);
}
List result = new ArrayList();
int i = 0;
int l = source.length();
int n = source.indexOf(delimiter);
while (n != -1) {
// zero - length items are not seen as tokens at start or end: ",," is one empty token but not three
if ((i < n) || ((i > 0) && (i < l))) {
result.add(trim ? source.substring(i, n).trim() : source.substring(i, n));
}
i = n + dl;
n = source.indexOf(delimiter, i);
}
// is there a non - empty String to cut from the tail?
if (n < 0) {
n = source.length();
}
if (i < n) {
result.add(trim ? source.substring(i).trim() : source.substring(i));
}
return result;
}
/**
* Splits a String into substrings along the provided paramDelim
delimiter,
* then each substring is treat as a key-value pair delimited by keyValDelim
.
*
* @param source the string to split
* @param paramDelim the string to delimit each key-value pair
* @param keyValDelim the string to delimit key and value
*
* @return a map of splitted key-value pairs
*/
public static Map splitAsMap(String source, String paramDelim, String keyValDelim) {
int keyValLen = keyValDelim.length();
// use LinkedHashMap to preserve the order of items
Map params = new LinkedHashMap();
Iterator itParams = CmsStringUtil.splitAsList(source, paramDelim, true).iterator();
while (itParams.hasNext()) {
String param = itParams.next();
int pos = param.indexOf(keyValDelim);
String key = param;
String value = "";
if (pos > 0) {
key = param.substring(0, pos);
if ((pos + keyValLen) < param.length()) {
value = param.substring(pos + keyValLen);
}
}
params.put(key, value);
}
return params;
}
/**
* Substitutes a pattern in a string using a {@link I_CmsRegexSubstitution}.
*
* @param pattern the pattern to substitute
* @param text the text in which the pattern should be substituted
* @param sub the substitution handler
*
* @return the transformed string
*/
public static String substitute(Pattern pattern, String text, I_CmsRegexSubstitution sub) {
StringBuffer buffer = new StringBuffer();
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
matcher.appendReplacement(buffer, sub.substituteMatch(text, matcher));
}
matcher.appendTail(buffer);
return buffer.toString();
}
/**
* Replaces a set of searchString
and replaceString
pairs,
* given by the substitutions
Map parameter.
*
* @param source the string to scan
* @param substitions the map of substitutions
*
* @return the substituted String
*
* @see #substitute(String, String, String)
*/
public static String substitute(String source, Map substitions) {
String result = source;
Iterator> it = substitions.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
result = substitute(result, entry.getKey(), entry.getValue().toString());
}
return result;
}
/**
* Substitutes searchString
in the given source String with replaceString
.
*
* This is a high-performance implementation which should be used as a replacement for
* {@link String#replaceAll(java.lang.String, java.lang.String)}
in case no
* regular expression evaluation is required.
*
* @param source the content which is scanned
* @param searchString the String which is searched in content
* @param replaceString the String which replaces searchString
*
* @return the substituted String
*/
public static String substitute(String source, String searchString, String replaceString) {
if (source == null) {
return null;
}
if (isEmpty(searchString)) {
return source;
}
if (replaceString == null) {
replaceString = "";
}
int len = source.length();
int sl = searchString.length();
int rl = replaceString.length();
int length;
if (sl == rl) {
length = len;
} else {
int c = 0;
int s = 0;
int e;
while ((e = source.indexOf(searchString, s)) != -1) {
c++;
s = e + sl;
}
if (c == 0) {
return source;
}
length = len - (c * (sl - rl));
}
int s = 0;
int e = source.indexOf(searchString, s);
if (e == -1) {
return source;
}
StringBuffer sb = new StringBuffer(length);
while (e != -1) {
sb.append(source.substring(s, e));
sb.append(replaceString);
s = e + sl;
e = source.indexOf(searchString, s);
}
e = len;
sb.append(source.substring(s, e));
return sb.toString();
}
/**
* Substitutes the OpenCms context path (e.g. /opencms/opencms/) in a HTML page with a
* special variable so that the content also runs if the context path of the server changes.
*
* @param htmlContent the HTML to replace the context path in
* @param context the context path of the server
*
* @return the HTML with the replaced context path
*/
public static String substituteContextPath(String htmlContent, String context) {
if (m_contextSearch == null) {
m_contextSearch = "([^\\w/])" + context;
m_contextReplace = "$1" + CmsStringUtil.escapePattern(CmsStringUtil.MACRO_OPENCMS_CONTEXT) + "/";
}
return substitutePerl(htmlContent, m_contextSearch, m_contextReplace, "g");
}
/**
* Substitutes searchString in content with replaceItem.
*
* @param content the content which is scanned
* @param searchString the String which is searched in content
* @param replaceItem the new String which replaces searchString
* @param occurences must be a "g" if all occurrences of searchString shall be replaced
*
* @return String the substituted String
*/
public static String substitutePerl(String content, String searchString, String replaceItem, String occurences) {
String translationRule = "s#" + searchString + "#" + replaceItem + "#" + occurences;
Perl5Util perlUtil = new Perl5Util();
try {
return perlUtil.substitute(translationRule, content);
} catch (MalformedPerl5PatternException e) {
if (LOG.isDebugEnabled()) {
LOG.debug(
Messages.get().getBundle().key(Messages.LOG_MALFORMED_TRANSLATION_RULE_1, translationRule),
e);
}
}
return content;
}
/**
* Returns the java String literal for the given String.
*
* This is the form of the String that had to be written into source code
* using the unicode escape sequence for special characters.
*
* Example: "Ä" would be transformed to "\\u00C4".
*
* @param s a string that may contain non-ascii characters
*
* @return the java unicode escaped string Literal of the given input string
*/
public static String toUnicodeLiteral(String s) {
StringBuffer result = new StringBuffer();
char[] carr = s.toCharArray();
String unicode;
for (int i = 0; i < carr.length; i++) {
result.append("\\u");
// append leading zeros
unicode = Integer.toHexString(carr[i]).toUpperCase();
for (int j = 4 - unicode.length(); j > 0; j--) {
result.append("0");
}
result.append(unicode);
}
return result.toString();
}
/**
* This method transformes a string which matched a format with one or more place holders into another format. The
* other format also includes the same number of place holders. Place holders start with
* {@link org.opencms.util.CmsStringUtil#PLACEHOLDER_START} and end with {@link org.opencms.util.CmsStringUtil#PLACEHOLDER_END}.
*
* @param oldFormat the original format
* @param newFormat the new format
* @param value the value which matched the original format and which shall be transformed into the new format
*
* @return the new value with the filled place holder with the information in the parameter value
*/
public static String transformValues(String oldFormat, String newFormat, String value) {
if (!oldFormat.contains(CmsStringUtil.PLACEHOLDER_START)
|| !oldFormat.contains(CmsStringUtil.PLACEHOLDER_END)
|| !newFormat.contains(CmsStringUtil.PLACEHOLDER_START)
|| !newFormat.contains(CmsStringUtil.PLACEHOLDER_END)) {
// no place holders are set in correct format
// that is why there is nothing to calculate and the value is the new format
return newFormat;
}
//initialize the arrays with the values where the place holders starts
ArrayList oldValues = new ArrayList();
ArrayList newValues = new ArrayList();
// count the number of placeholders
// for example these are three pairs:
// old format: {.*}{.*}{.*}
// new format: {}{}{}
// get the number of place holders in the old format
int oldNumber = 0;
try {
int counter = 0;
Pattern pattern = Pattern.compile("\\{\\.\\*\\}");
Matcher matcher = pattern.matcher(oldFormat);
// get the number of matches
while (matcher.find()) {
counter += 1;
}
oldValues = new ArrayList(counter);
matcher = pattern.matcher(oldFormat);
while (matcher.find()) {
int start = matcher.start() + 1;
oldValues.add(oldNumber, new Integer(start));
oldNumber += 1;
}
} catch (PatternSyntaxException e) {
// do nothing
}
// get the number of place holders in the new format
int newNumber = 0;
try {
int counter = 0;
Pattern pattern = Pattern.compile("\\{\\}");
Matcher matcher = pattern.matcher(newFormat);
// get the number of matches
while (matcher.find()) {
counter += 1;
}
newValues = new ArrayList(counter);
matcher = pattern.matcher(newFormat);
while (matcher.find()) {
int start = matcher.start() + 1;
newValues.add(newNumber, new Integer(start));
newNumber += 1;
}
} catch (PatternSyntaxException e) {
// do nothing
}
// prove the numbers of place holders
if (oldNumber != newNumber) {
// not the same number of place holders in the old and in the new format
return newFormat;
}
// initialize the arrays with the values between the place holders
ArrayList oldBetween = new ArrayList(oldNumber + 1);
ArrayList newBetween = new ArrayList(newNumber + 1);
// get the values between the place holders for the old format
// for this example with oldFormat: {.*}{.*}{.*}
// this array is that:
// ---------
// | empty |
// ---------
// | |
// |--------
// | |
// |--------
// | empty |
// |--------
int counter = 0;
Iterator iter = oldValues.iterator();
while (iter.hasNext()) {
int start = iter.next().intValue();
if (counter == 0) {
// the first entry
if (start == 1) {
// the first place holder starts at the beginning of the old format
// for example: {.*}...
oldBetween.add(counter, "");
} else {
// the first place holder starts NOT at the beginning of the old format
// for example: {.*}...
String part = oldFormat.substring(0, start - 1);
oldBetween.add(counter, part);
}
} else {
// the entries between the first and the last entry
int lastStart = oldValues.get(counter - 1).intValue();
String part = oldFormat.substring(lastStart + 3, start - 1);
oldBetween.add(counter, part);
}
counter += 1;
}
// the last element
int lastElstart = oldValues.get(counter - 1).intValue();
if ((lastElstart + 2) == (oldFormat.length() - 1)) {
// the last place holder ends at the end of the old format
// for example: ...{.*}
oldBetween.add(counter, "");
} else {
// the last place holder ends NOT at the end of the old format
// for example: ...{.*}
String part = oldFormat.substring(lastElstart + 3);
oldBetween.add(counter, part);
}
// get the values between the place holders for the new format
// for this example with newFormat: {}{}{}
// this array is that:
// ------------|
// | empty |
// ------------|
// | |
// |-----------|
// | |
// |-----------|
// | empty |
// |-----------|
counter = 0;
iter = newValues.iterator();
while (iter.hasNext()) {
int start = iter.next().intValue();
if (counter == 0) {
// the first entry
if (start == 1) {
// the first place holder starts at the beginning of the new format
// for example: {.*}...
newBetween.add(counter, "");
} else {
// the first place holder starts NOT at the beginning of the new format
// for example: {.*}...
String part = newFormat.substring(0, start - 1);
newBetween.add(counter, part);
}
} else {
// the entries between the first and the last entry
int lastStart = newValues.get(counter - 1).intValue();
String part = newFormat.substring(lastStart + 1, start - 1);
newBetween.add(counter, part);
}
counter += 1;
}
// the last element
lastElstart = newValues.get(counter - 1).intValue();
if ((lastElstart + 2) == (newFormat.length() - 1)) {
// the last place holder ends at the end of the old format
// for example: ...{.*}
newBetween.add(counter, "");
} else {
// the last place holder ends NOT at the end of the old format
// for example: ...{.*}
String part = newFormat.substring(lastElstart + 1);
newBetween.add(counter, part);
}
// get the values in the place holders
// for the example with:
// oldFormat: {.*}{.*}{.*}
// newFormat: {}{}{}
// value: abcdefghi
// it is used the array with the old values between the place holders to get the content in the place holders
// this result array is that:
// ------|
// | abc |
// ------|
// | def |
// |-----|
// | ghi |
// |-----|
ArrayList placeHolders = new ArrayList(oldNumber);
String tmpValue = value;
// loop over all rows with the old values between the place holders and take the values between them in the
// current property value
for (int placeCounter = 0; placeCounter < (oldBetween.size() - 1); placeCounter++) {
// get the two next values with the old values between the place holders
String content = oldBetween.get(placeCounter);
String nextContent = oldBetween.get(placeCounter + 1);
// check the position of the first of the next values in the current property value
int contPos = 0;
int nextContPos = 0;
if ((placeCounter == 0) && CmsStringUtil.isEmpty(content)) {
// the first value in the values between the place holders is empty
// for example: {.*}...
contPos = 0;
} else {
// the first value in the values between the place holders is NOT empty
// for example: bla{.*}
...
contPos = tmpValue.indexOf(content);
}
// check the position of the second of the next values in the current property value
if (((placeCounter + 1) == (oldBetween.size() - 1)) && CmsStringUtil.isEmpty(nextContent)) {
// the last value in the values between the place holders is empty
// for example: ...
{.*}
nextContPos = tmpValue.length();
} else {
// the last value in the values between the place holders is NOT empty
// for example: ...
{.*}bla
nextContPos = tmpValue.indexOf(nextContent);
}
// every value must match the current value
if ((contPos < 0) || (nextContPos < 0)) {
return value;
}
// get the content of the current place holder
String placeContent = tmpValue.substring(contPos + content.length(), nextContPos);
placeHolders.add(placeCounter, placeContent);
// cut off the currently visited part of the value
tmpValue = tmpValue.substring(nextContPos);
}
// build the new format
// with following vectors from above:
// old values between the place holders:
// ---------
// | empty | (old.1)
// ---------
// | | (old.2)
// |--------
// | | (old.3)
// |--------
// | empty | (old.4)
// |--------
//
// new values between the place holders:
// ------------|
// | empty | (new.1)
// ------------|
// | | (new.2)
// |-----------|
// | | (new.3)
// |-----------|
// | empty | (new.4)
// |-----------|
//
// content of the place holders:
// ------|
// | abc | (place.1)
// ------|
// | def | (place.2)
// |-----|
// | ghi | (place.3)
// |-----|
//
// the result is calculated in that way:
// new.1 + place.1 + new.2 + place.2 + new.3 + place.3 + new.4
String newValue = "";
// take the values between the place holders and add the content of the place holders
for (int buildCounter = 0; buildCounter < newNumber; buildCounter++) {
newValue = newValue + newBetween.get(buildCounter) + placeHolders.get(buildCounter);
}
newValue = newValue + newBetween.get(newNumber);
// return the changed value
return newValue;
}
/**
* Returns a substring of the source, which is at most length characters long.
*
* This is the same as calling {@link #trimToSize(String, int, String)} with the
* parameters (source, length, " ...")
.
*
* @param source the string to trim
* @param length the maximum length of the string to be returned
*
* @return a substring of the source, which is at most length characters long
*/
public static String trimToSize(String source, int length) {
return trimToSize(source, length, length, " ...");
}
/**
* Returns a substring of the source, which is at most length characters long, cut
* in the last area
chars in the source at a sentence ending char or whitespace.
*
* If a char is cut, the given suffix
is appended to the result.
*
* @param source the string to trim
* @param length the maximum length of the string to be returned
* @param area the area at the end of the string in which to find a sentence ender or whitespace
* @param suffix the suffix to append in case the String was trimmed
*
* @return a substring of the source, which is at most length characters long
*/
public static String trimToSize(String source, int length, int area, String suffix) {
if ((source == null) || (source.length() <= length)) {
// no operation is required
return source;
}
if (CmsStringUtil.isEmpty(suffix)) {
// we need an empty suffix
suffix = "";
}
// must remove the length from the after sequence chars since these are always added in the end
int modLength = length - suffix.length();
if (modLength <= 0) {
// we are to short, return beginning of the suffix
return suffix.substring(0, length);
}
int modArea = area + suffix.length();
if ((modArea > modLength) || (modArea < 0)) {
// area must not be longer then max length
modArea = modLength;
}
// first reduce the String to the maximum allowed length
String findPointSource = source.substring(modLength - modArea, modLength);
String result;
// try to find an "sentence ending" char in the text
int pos = lastIndexOf(findPointSource, SENTENCE_ENDING_CHARS);
if (pos >= 0) {
// found a sentence ender in the lookup area, keep the sentence ender
result = source.substring(0, (modLength - modArea) + pos + 1) + suffix;
} else {
// no sentence ender was found, try to find a whitespace
pos = lastWhitespaceIn(findPointSource);
if (pos >= 0) {
// found a whitespace, don't keep the whitespace
result = source.substring(0, (modLength - modArea) + pos) + suffix;
} else {
// not even a whitespace was found, just cut away what's to long
result = source.substring(0, modLength) + suffix;
}
}
return result;
}
/**
* Returns a substring of the source, which is at most length characters long.
*
* If a char is cut, the given suffix
is appended to the result.
*
* This is almost the same as calling {@link #trimToSize(String, int, int, String)} with the
* parameters (source, length, length*, suffix)
. If length
* if larger then 100, then length* = length / 2
,
* otherwise length* = length
.
*
* @param source the string to trim
* @param length the maximum length of the string to be returned
* @param suffix the suffix to append in case the String was trimmed
*
* @return a substring of the source, which is at most length characters long
*/
public static String trimToSize(String source, int length, String suffix) {
int area = (length > 100) ? length / 2 : length;
return trimToSize(source, length, area, suffix);
}
/**
* Validates a value against a regular expression.
*
* @param value the value to test
* @param regex the regular expression
* @param allowEmpty if an empty value is allowed
*
* @return true
if the value satisfies the validation
*/
public static boolean validateRegex(String value, String regex, boolean allowEmpty) {
if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
return allowEmpty;
}
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(value);
return matcher.matches();
}
}