001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.util; 029 030import org.opencms.file.CmsResource; 031import org.opencms.i18n.CmsEncoder; 032import org.opencms.i18n.I_CmsMessageBundle; 033import org.opencms.main.CmsIllegalArgumentException; 034import org.opencms.main.CmsLog; 035import org.opencms.main.OpenCms; 036 037import java.awt.Color; 038import java.net.InetAddress; 039import java.net.NetworkInterface; 040import java.nio.charset.Charset; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.Collection; 044import java.util.Comparator; 045import java.util.HashMap; 046import java.util.Iterator; 047import java.util.LinkedHashMap; 048import java.util.List; 049import java.util.Locale; 050import java.util.Map; 051import java.util.regex.Matcher; 052import java.util.regex.Pattern; 053import java.util.regex.PatternSyntaxException; 054 055import org.apache.commons.logging.Log; 056import org.apache.oro.text.perl.MalformedPerl5PatternException; 057import org.apache.oro.text.perl.Perl5Util; 058 059import com.cybozu.labs.langdetect.Detector; 060import com.cybozu.labs.langdetect.DetectorFactory; 061import com.cybozu.labs.langdetect.LangDetectException; 062 063/** 064 * Provides String utility functions.<p> 065 * 066 * @since 6.0.0 067 */ 068public final class CmsStringUtil { 069 070 /** 071 * Compares two Strings according to the count of containing slashes.<p> 072 * 073 * If both Strings contain the same count of slashes the Strings are compared.<p> 074 */ 075 public static class CmsSlashComparator implements Comparator<String> { 076 077 /** 078 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 079 */ 080 public int compare(String a, String b) { 081 082 int slashCountA = countChar(a, '/'); 083 int slashCountB = countChar(b, '/'); 084 085 if (slashCountA < slashCountB) { 086 return 1; 087 } else if (slashCountA == slashCountB) { 088 return a.compareTo(b); 089 } else { 090 return -1; 091 } 092 } 093 } 094 095 /** Regular expression that matches the HTML body end tag. */ 096 public static final String BODY_END_REGEX = "<\\s*/\\s*body[^>]*>"; 097 098 /** Regular expression that matches the HTML body start tag. */ 099 public static final String BODY_START_REGEX = "<\\s*body[^>]*>"; 100 101 /** Constant for <code>"false"</code>. */ 102 public static final String FALSE = Boolean.toString(false); 103 104 /** a convenient shorthand to the line separator constant. */ 105 public static final String LINE_SEPARATOR = System.getProperty("line.separator"); 106 107 /** Context macro. */ 108 public static final String MACRO_OPENCMS_CONTEXT = "${OpenCmsContext}"; 109 110 /** Pattern to determine a locale for suffixes like '_de' or '_en_US'. */ 111 public static final Pattern PATTERN_LOCALE_SUFFIX = Pattern.compile("(.*)_([a-z]{2}(?:_[A-Z]{2})?)(?:\\.[^\\.]*)?$"); 112 113 /** Pattern to determine the document number for suffixes like '_0001'. */ 114 public static final Pattern PATTERN_NUMBER_SUFFIX = Pattern.compile("(.*)_(\\d+)(\\.[^\\.^\\n]*)?$"); 115 116 /** The place holder end sign in the pattern. */ 117 public static final String PLACEHOLDER_END = "}"; 118 119 /** The place holder start sign in the pattern. */ 120 public static final String PLACEHOLDER_START = "{"; 121 122 /** Contains all chars that end a sentence in the {@link #trimToSize(String, int, int, String)} method. */ 123 public static final char[] SENTENCE_ENDING_CHARS = {'.', '!', '?'}; 124 125 /** a convenient shorthand for tabulations. */ 126 public static final String TABULATOR = " "; 127 128 /** Constant for <code>"true"</code>. */ 129 public static final String TRUE = Boolean.toString(true); 130 131 /** Regex pattern that matches an end body tag. */ 132 private static final Pattern BODY_END_PATTERN = Pattern.compile(BODY_END_REGEX, Pattern.CASE_INSENSITIVE); 133 134 /** Regex pattern that matches a start body tag. */ 135 private static final Pattern BODY_START_PATTERN = Pattern.compile(BODY_START_REGEX, Pattern.CASE_INSENSITIVE); 136 137 /** Day constant. */ 138 private static final long DAYS = 1000 * 60 * 60 * 24; 139 140 /** Hour constant. */ 141 private static final long HOURS = 1000 * 60 * 60; 142 143 /** The log object for this class. */ 144 private static final Log LOG = CmsLog.getLog(CmsStringUtil.class); 145 146 /** OpenCms context replace String, static for performance reasons. */ 147 private static String m_contextReplace; 148 149 /** OpenCms context search String, static for performance reasons. */ 150 private static String m_contextSearch; 151 152 /** Minute constant. */ 153 private static final long MINUTES = 1000 * 60; 154 155 /** Second constant. */ 156 private static final long SECONDS = 1000; 157 158 /** Regex that matches an encoding String in an xml head. */ 159 private static final Pattern XML_ENCODING_REGEX = Pattern.compile( 160 "encoding\\s*=\\s*[\"'].+[\"']", 161 Pattern.CASE_INSENSITIVE); 162 163 /** Regex that matches an xml head. */ 164 private static final Pattern XML_HEAD_REGEX = Pattern.compile("<\\s*\\?.*\\?\\s*>", Pattern.CASE_INSENSITIVE); 165 166 /** 167 * Default constructor (empty), private because this class has only 168 * static methods.<p> 169 */ 170 private CmsStringUtil() { 171 172 // empty 173 } 174 175 /** 176 * Adds leading and trailing slashes to a path.<p> 177 * 178 * @param path the path to which add the slashes 179 * 180 * @return the path with added leading and trailing slashes 181 */ 182 public static String addLeadingAndTrailingSlash(String path) { 183 184 StringBuffer buffer1 = new StringBuffer(); 185 if (!path.startsWith("/")) { 186 buffer1.append("/"); 187 } 188 buffer1.append(path); 189 if (!path.endsWith("/")) { 190 buffer1.append("/"); 191 } 192 return buffer1.toString(); 193 } 194 195 /** 196 * Returns a string representation for the given array using the given separator.<p> 197 * 198 * @param arg the array to transform to a String 199 * @param separator the item separator 200 * 201 * @return the String of the given array 202 */ 203 public static String arrayAsString(final String[] arg, String separator) { 204 205 StringBuffer result = new StringBuffer(); 206 for (int i = 0; i < arg.length; i++) { 207 result.append(arg[i]); 208 if ((i + 1) < arg.length) { 209 result.append(separator); 210 } 211 } 212 return result.toString(); 213 } 214 215 /** 216 * Changes the filename suffix. 217 * 218 * @param filename the filename to be changed 219 * @param suffix the new suffix of the file 220 * 221 * @return the filename with the replaced suffix 222 */ 223 public static String changeFileNameSuffixTo(String filename, String suffix) { 224 225 int dotPos = filename.lastIndexOf('.'); 226 if (dotPos != -1) { 227 return filename.substring(0, dotPos + 1) + suffix; 228 } else { 229 // the string has no suffix 230 return filename; 231 } 232 } 233 234 /** 235 * Checks if a given name is composed only of the characters <code>a...z,A...Z,0...9</code> 236 * and the provided <code>constraints</code>.<p> 237 * 238 * If the check fails, an Exception is generated. The provided bundle and key is 239 * used to generate the Exception. 4 parameters are passed to the Exception:<ol> 240 * <li>The <code>name</code> 241 * <li>The first illegal character found 242 * <li>The position where the illegal character was found 243 * <li>The <code>constraints</code></ol> 244 * 245 * @param name the name to check 246 * @param contraints the additional character constraints 247 * @param key the key to use for generating the Exception (if required) 248 * @param bundle the bundle to use for generating the Exception (if required) 249 * 250 * @throws CmsIllegalArgumentException if the check fails (generated from the given key and bundle) 251 */ 252 public static void checkName(String name, String contraints, String key, I_CmsMessageBundle bundle) 253 throws CmsIllegalArgumentException { 254 255 int l = name.length(); 256 for (int i = 0; i < l; i++) { 257 char c = name.charAt(i); 258 if (((c < 'a') || (c > 'z')) 259 && ((c < '0') || (c > '9')) 260 && ((c < 'A') || (c > 'Z')) 261 && (contraints.indexOf(c) < 0)) { 262 263 throw new CmsIllegalArgumentException(bundle.container(key, new Object[] { 264 name, 265 new Character(c), 266 new Integer(i), 267 contraints})); 268 } 269 } 270 } 271 272 /** 273 * Returns a string representation for the given collection using the given separator.<p> 274 * 275 * @param collection the collection to print 276 * @param separator the item separator 277 * 278 * @return the string representation for the given collection 279 */ 280 public static String collectionAsString(Collection<?> collection, String separator) { 281 282 StringBuffer string = new StringBuffer(128); 283 Iterator<?> it = collection.iterator(); 284 while (it.hasNext()) { 285 string.append(it.next()); 286 if (it.hasNext()) { 287 string.append(separator); 288 } 289 } 290 return string.toString(); 291 } 292 293 /** 294 * Compares paths while ignoring leading / trailing slashes.<p> 295 * 296 * @param path1 the first path 297 * @param path2 the second path 298 * @return true if the paths are equal (ignoring leading or trailing slashes) 299 */ 300 public static boolean comparePaths(String path1, String path2) { 301 302 return addLeadingAndTrailingSlash(path1).equals(addLeadingAndTrailingSlash(path2)); 303 } 304 305 /** 306 * Counts the occurrence of a given char in a given String.<p> 307 * 308 * @param s the string 309 * @param c the char to count 310 * 311 * @return returns the count of occurrences of a given char in a given String 312 */ 313 public static int countChar(String s, char c) { 314 315 int counter = 0; 316 for (int i = 0; i < s.length(); i++) { 317 if (s.charAt(i) == c) { 318 counter++; 319 } 320 } 321 return counter; 322 } 323 324 /** 325 * Returns a String array representation for the given enum.<p> 326 * 327 * @param <T> the type of the enum 328 * @param values the enum values 329 * 330 * @return the representing String array 331 */ 332 public static <T extends Enum<T>> String[] enumNameToStringArray(T[] values) { 333 334 int i = 0; 335 String[] result = new String[values.length]; 336 for (T value : values) { 337 result[i++] = value.name(); 338 } 339 return result; 340 } 341 342 /** 343 * Replaces occurrences of special control characters in the given input with 344 * a HTML representation.<p> 345 * 346 * This method currently replaces line breaks to <code><br/></code> and special HTML chars 347 * like <code>< > & "</code> with their HTML entity representation.<p> 348 * 349 * @param source the String to escape 350 * 351 * @return the escaped String 352 */ 353 public static String escapeHtml(String source) { 354 355 if (source == null) { 356 return null; 357 } 358 source = CmsEncoder.escapeXml(source); 359 source = CmsStringUtil.substitute(source, "\r", ""); 360 source = CmsStringUtil.substitute(source, "\n", "<br/>\n"); 361 return source; 362 } 363 364 /** 365 * Escapes a String so it may be used in JavaScript String definitions.<p> 366 * 367 * This method replaces line breaks, quotation marks and \ characters.<p> 368 * 369 * @param source the String to escape 370 * 371 * @return the escaped String 372 */ 373 public static String escapeJavaScript(String source) { 374 375 source = CmsStringUtil.substitute(source, "\\", "\\\\"); 376 source = CmsStringUtil.substitute(source, "\"", "\\\""); 377 source = CmsStringUtil.substitute(source, "\'", "\\\'"); 378 source = CmsStringUtil.substitute(source, "\r\n", "\\n"); 379 source = CmsStringUtil.substitute(source, "\n", "\\n"); 380 381 // to avoid XSS (closing script tags) in embedded Javascript 382 source = CmsStringUtil.substitute(source, "/", "\\/"); 383 return source; 384 } 385 386 /** 387 * Escapes a String so it may be used as a Perl5 regular expression.<p> 388 * 389 * This method replaces the following characters in a String:<br> 390 * <code>{}[]()\$^.*+/</code><p> 391 * 392 * @param source the string to escape 393 * 394 * @return the escaped string 395 */ 396 public static String escapePattern(String source) { 397 398 if (source == null) { 399 return null; 400 } 401 StringBuffer result = new StringBuffer(source.length() * 2); 402 for (int i = 0; i < source.length(); ++i) { 403 char ch = source.charAt(i); 404 switch (ch) { 405 case '\\': 406 result.append("\\\\"); 407 break; 408 case '/': 409 result.append("\\/"); 410 break; 411 case '$': 412 result.append("\\$"); 413 break; 414 case '^': 415 result.append("\\^"); 416 break; 417 case '.': 418 result.append("\\."); 419 break; 420 case '*': 421 result.append("\\*"); 422 break; 423 case '+': 424 result.append("\\+"); 425 break; 426 case '|': 427 result.append("\\|"); 428 break; 429 case '?': 430 result.append("\\?"); 431 break; 432 case '{': 433 result.append("\\{"); 434 break; 435 case '}': 436 result.append("\\}"); 437 break; 438 case '[': 439 result.append("\\["); 440 break; 441 case ']': 442 result.append("\\]"); 443 break; 444 case '(': 445 result.append("\\("); 446 break; 447 case ')': 448 result.append("\\)"); 449 break; 450 default: 451 result.append(ch); 452 } 453 } 454 return new String(result); 455 } 456 457 /** 458 * This method takes a part of a html tag definition, an attribute to extend within the 459 * given text and a default value for this attribute; and returns a <code>{@link Map}</code> 460 * with 2 values: a <code>{@link String}</code> with key <code>"text"</code> with the new text 461 * without the given attribute, and another <code>{@link String}</code> with key <code>"value"</code> 462 * with the new extended value for the given attribute, this value is surrounded by the same type of 463 * quotation marks as in the given text.<p> 464 * 465 * @param text the text to search in 466 * @param attribute the attribute to remove and extend from the text 467 * @param defValue a default value for the attribute, should not have any quotation mark 468 * 469 * @return a map with the new text and the new value for the given attribute 470 */ 471 public static Map<String, String> extendAttribute(String text, String attribute, String defValue) { 472 473 Map<String, String> retValue = new HashMap<String, String>(); 474 retValue.put("text", text); 475 retValue.put("value", "'" + defValue + "'"); 476 if ((text != null) && (text.toLowerCase().indexOf(attribute.toLowerCase()) >= 0)) { 477 // this does not work for things like "att=method()" without quotations. 478 String quotation = "\'"; 479 int pos1 = text.toLowerCase().indexOf(attribute.toLowerCase()); 480 // looking for the opening quotation mark 481 int pos2 = text.indexOf(quotation, pos1); 482 int test = text.indexOf("\"", pos1); 483 if ((test > -1) && ((pos2 == -1) || (test < pos2))) { 484 quotation = "\""; 485 pos2 = test; 486 } 487 // assuming there is a closing quotation mark 488 int pos3 = text.indexOf(quotation, pos2 + 1); 489 // building the new attribute value 490 String newValue = quotation + defValue + text.substring(pos2 + 1, pos3 + 1); 491 // removing the onload statement from the parameters 492 String newText = text.substring(0, pos1); 493 if (pos3 < text.length()) { 494 newText += text.substring(pos3 + 1); 495 } 496 retValue.put("text", newText); 497 retValue.put("value", newValue); 498 } 499 return retValue; 500 } 501 502 /** 503 * Extracts the content of a <code><body></code> tag in a HTML page.<p> 504 * 505 * This method should be pretty robust and work even if the input HTML does not contains 506 * a valid body tag.<p> 507 * 508 * @param content the content to extract the body from 509 * 510 * @return the extracted body tag content 511 */ 512 public static String extractHtmlBody(String content) { 513 514 Matcher startMatcher = BODY_START_PATTERN.matcher(content); 515 Matcher endMatcher = BODY_END_PATTERN.matcher(content); 516 517 int start = 0; 518 int end = content.length(); 519 520 if (startMatcher.find()) { 521 start = startMatcher.end(); 522 } 523 524 if (endMatcher.find(start)) { 525 end = endMatcher.start(); 526 } 527 528 return content.substring(start, end); 529 } 530 531 /** 532 * Extracts the xml encoding setting from an xml file that is contained in a String by parsing 533 * the xml head.<p> 534 * 535 * This is useful if you have a byte array that contains a xml String, 536 * but you do not know the xml encoding setting. Since the encoding setting 537 * in the xml head is usually encoded with standard US-ASCII, you usually 538 * just create a String of the byte array without encoding setting, 539 * and use this method to find the 'true' encoding. Then create a String 540 * of the byte array again, this time using the found encoding.<p> 541 * 542 * This method will return <code>null</code> in case no xml head 543 * or encoding information is contained in the input.<p> 544 * 545 * @param content the xml content to extract the encoding from 546 * 547 * @return the extracted encoding, or null if no xml encoding setting was found in the input 548 */ 549 public static String extractXmlEncoding(String content) { 550 551 String result = null; 552 Matcher xmlHeadMatcher = XML_HEAD_REGEX.matcher(content); 553 if (xmlHeadMatcher.find()) { 554 String xmlHead = xmlHeadMatcher.group(); 555 Matcher encodingMatcher = XML_ENCODING_REGEX.matcher(xmlHead); 556 if (encodingMatcher.find()) { 557 String encoding = encodingMatcher.group(); 558 int pos1 = encoding.indexOf('=') + 2; 559 String charset = encoding.substring(pos1, encoding.length() - 1); 560 if (Charset.isSupported(charset)) { 561 result = charset; 562 } 563 } 564 } 565 return result; 566 } 567 568 /** 569 * Formats a resource name that it is displayed with the maximum length and path information is adjusted.<p> 570 * In order to reduce the length of the displayed names, single folder names are removed/replaced with ... successively, 571 * starting with the second! folder. The first folder is removed as last.<p> 572 * 573 * Example: formatResourceName("/myfolder/subfolder/index.html", 21) returns <code>/myfolder/.../index.html</code>.<p> 574 * 575 * @param name the resource name to format 576 * @param maxLength the maximum length of the resource name (without leading <code>/...</code>) 577 * 578 * @return the formatted resource name 579 */ 580 public static String formatResourceName(String name, int maxLength) { 581 582 if (name == null) { 583 return null; 584 } 585 586 if (name.length() <= maxLength) { 587 return name; 588 } 589 590 int total = name.length(); 591 String[] names = CmsStringUtil.splitAsArray(name, "/"); 592 if (name.endsWith("/")) { 593 names[names.length - 1] = names[names.length - 1] + "/"; 594 } 595 for (int i = 1; (total > maxLength) && (i < (names.length - 1)); i++) { 596 if (i > 1) { 597 names[i - 1] = ""; 598 } 599 names[i] = "..."; 600 total = 0; 601 for (int j = 0; j < names.length; j++) { 602 int l = names[j].length(); 603 total += l + ((l > 0) ? 1 : 0); 604 } 605 } 606 if (total > maxLength) { 607 names[0] = (names.length > 2) ? "" : (names.length > 1) ? "..." : names[0]; 608 } 609 610 StringBuffer result = new StringBuffer(); 611 for (int i = 0; i < names.length; i++) { 612 if (names[i].length() > 0) { 613 result.append("/"); 614 result.append(names[i]); 615 } 616 } 617 618 return result.toString(); 619 } 620 621 /** 622 * Formats a runtime in the format hh:mm:ss, to be used e.g. in reports.<p> 623 * 624 * If the runtime is greater then 24 hours, the format dd:hh:mm:ss is used.<p> 625 * 626 * @param runtime the time to format 627 * 628 * @return the formatted runtime 629 */ 630 public static String formatRuntime(long runtime) { 631 632 long seconds = (runtime / SECONDS) % 60; 633 long minutes = (runtime / MINUTES) % 60; 634 long hours = (runtime / HOURS) % 24; 635 long days = runtime / DAYS; 636 StringBuffer strBuf = new StringBuffer(); 637 638 if (days > 0) { 639 if (days < 10) { 640 strBuf.append('0'); 641 } 642 strBuf.append(days); 643 strBuf.append(':'); 644 } 645 646 if (hours < 10) { 647 strBuf.append('0'); 648 } 649 strBuf.append(hours); 650 strBuf.append(':'); 651 652 if (minutes < 10) { 653 strBuf.append('0'); 654 } 655 strBuf.append(minutes); 656 strBuf.append(':'); 657 658 if (seconds < 10) { 659 strBuf.append('0'); 660 } 661 strBuf.append(seconds); 662 663 return strBuf.toString(); 664 } 665 666 /** 667 * Returns the color value (<code>{@link Color}</code>) for the given String value.<p> 668 * 669 * All parse errors are caught and the given default value is returned in this case.<p> 670 * 671 * @param value the value to parse as color 672 * @param defaultValue the default value in case of parsing errors 673 * @param key a key to be included in the debug output in case of parse errors 674 * 675 * @return the int value for the given parameter value String 676 */ 677 public static Color getColorValue(String value, Color defaultValue, String key) { 678 679 Color result; 680 try { 681 char pre = value.charAt(0); 682 if (pre != '#') { 683 value = "#" + value; 684 } 685 result = Color.decode(value); 686 } catch (Exception e) { 687 if (LOG.isDebugEnabled()) { 688 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_COLOR_2, value, key)); 689 } 690 result = defaultValue; 691 } 692 return result; 693 } 694 695 /** 696 * Gets the common prefix path of two paths.<p> 697 * 698 * @param first the first path 699 * @param second the second path 700 * 701 * @return the common prefix path 702 */ 703 public static String getCommonPrefixPath(String first, String second) { 704 705 List<String> firstComponents = getPathComponents(first); 706 List<String> secondComponents = getPathComponents(second); 707 int minSize = Math.min(firstComponents.size(), secondComponents.size()); 708 StringBuffer resultBuffer = new StringBuffer(); 709 for (int i = 0; i < minSize; i++) { 710 if (firstComponents.get(i).equals(secondComponents.get(i))) { 711 resultBuffer.append("/"); 712 resultBuffer.append(firstComponents.get(i)); 713 } else { 714 break; 715 } 716 } 717 String result = resultBuffer.toString(); 718 if (result.length() == 0) { 719 result = "/"; 720 } 721 return result; 722 } 723 724 /** 725 * Returns the Ethernet-Address of the locale host.<p> 726 * 727 * A dummy ethernet address is returned, if the ip is 728 * representing the loopback address or in case of exceptions.<p> 729 * 730 * @return the Ethernet-Address 731 */ 732 public static String getEthernetAddress() { 733 734 try { 735 InetAddress ip = InetAddress.getLocalHost(); 736 if (!ip.isLoopbackAddress()) { 737 NetworkInterface network = NetworkInterface.getByInetAddress(ip); 738 byte[] mac = network.getHardwareAddress(); 739 StringBuilder sb = new StringBuilder(); 740 for (int i = 0; i < mac.length; i++) { 741 sb.append(String.format("%02X%s", new Byte(mac[i]), (i < (mac.length - 1)) ? ":" : "")); 742 } 743 return sb.toString(); 744 } 745 } catch (Throwable t) { 746 // if an exception occurred return a dummy address 747 } 748 // return a dummy ethernet address, if the ip is representing the loopback address or in case of exceptions 749 return CmsUUID.getDummyEthernetAddress(); 750 } 751 752 /** 753 * Returns the Integer (int) value for the given String value.<p> 754 * 755 * All parse errors are caught and the given default value is returned in this case.<p> 756 * 757 * @param value the value to parse as int 758 * @param defaultValue the default value in case of parsing errors 759 * @param key a key to be included in the debug output in case of parse errors 760 * 761 * @return the int value for the given parameter value String 762 */ 763 public static int getIntValue(String value, int defaultValue, String key) { 764 765 int result; 766 try { 767 result = Integer.valueOf(value).intValue(); 768 } catch (Exception e) { 769 if (LOG.isDebugEnabled()) { 770 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_INT_2, value, key)); 771 } 772 result = defaultValue; 773 } 774 return result; 775 } 776 777 /** 778 * Returns the closest Integer (int) value for the given String value.<p> 779 * 780 * All parse errors are caught and the given default value is returned in this case.<p> 781 * 782 * @param value the value to parse as int, can also represent a float value 783 * @param defaultValue the default value in case of parsing errors 784 * @param key a key to be included in the debug output in case of parse errors 785 * 786 * @return the closest int value for the given parameter value String 787 */ 788 public static int getIntValueRounded(String value, int defaultValue, String key) { 789 790 int result; 791 try { 792 result = Math.round(Float.parseFloat(value)); 793 } catch (Exception e) { 794 if (LOG.isDebugEnabled()) { 795 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_INT_2, value, key)); 796 } 797 result = defaultValue; 798 } 799 return result; 800 } 801 802 /** 803 * Returns the locale by suffix for the given name, including optional country code.<p> 804 * 805 * Calls {@link CmsResource#getName(String)} first, so the given name can also be a resource root path.<p> 806 * 807 * @param name the name to get the locale for 808 * 809 * @return the locale, or <code>null</code> 810 * 811 * @see #getLocaleSuffixForName(String) 812 */ 813 public static Locale getLocaleForName(String name) { 814 815 String suffix = getLocaleSuffixForName(CmsResource.getName(name)); 816 if (suffix != null) { 817 String laguageString = suffix.substring(0, 2); 818 return suffix.length() == 5 ? new Locale(laguageString, suffix.substring(3, 5)) : new Locale(laguageString); 819 } 820 return null; 821 } 822 823 /** 824 * Returns the locale for the given text based on the language detection library.<p> 825 * 826 * The result will be <code>null</code> if the detection fails or the detected locale is not configured 827 * in the 'opencms-system.xml' as available locale.<p> 828 * 829 * @param text the text to retrieve the locale for 830 * 831 * @return the detected locale for the given text 832 */ 833 public static Locale getLocaleForText(String text) { 834 835 // try to detect locale by language detector 836 if (isNotEmptyOrWhitespaceOnly(text)) { 837 try { 838 Detector detector = DetectorFactory.create(); 839 detector.append(text); 840 String lang = detector.detect(); 841 Locale loc = new Locale(lang); 842 if (OpenCms.getLocaleManager().getAvailableLocales().contains(loc)) { 843 return loc; 844 } 845 } catch (LangDetectException e) { 846 LOG.debug(e); 847 } 848 } 849 return null; 850 } 851 852 /** 853 * Returns the locale suffix as String for a given name, <code>null</code> otherwise.<p> 854 * 855 * Uses the the {@link #PATTERN_LOCALE_SUFFIX} to find a language_country occurrence in the 856 * given name and returns the first group of the match.<p> 857 * 858 * <b>Examples:</b> 859 * 860 * <ul> 861 * <li><code>rabbit_en_EN.html -> Locale[en_EN]</code> 862 * <li><code>rabbit_en_EN -> Locale[en_EN]</code> 863 * <li><code>rabbit_en.html -> Locale[en]</code> 864 * <li><code>rabbit_en -> Locale[en]</code> 865 * <li><code>rabbit_en. -> Locale[en]</code> 866 * <li><code>rabbit_enr -> null</code> 867 * <li><code>rabbit_en.tar.gz -> null</code> 868 * </ul> 869 * 870 * @param name the resource name to get the locale suffix for 871 * 872 * @return the locale suffix if found, <code>null</code> otherwise 873 */ 874 public static String getLocaleSuffixForName(String name) { 875 876 Matcher matcher = PATTERN_LOCALE_SUFFIX.matcher(name); 877 if (matcher.find()) { 878 return matcher.group(2); 879 } 880 return null; 881 } 882 883 /** 884 * Returns the Long (long) value for the given String value.<p> 885 * 886 * All parse errors are caught and the given default value is returned in this case.<p> 887 * 888 * @param value the value to parse as long 889 * @param defaultValue the default value in case of parsing errors 890 * @param key a key to be included in the debug output in case of parse errors 891 * 892 * @return the long value for the given parameter value String 893 */ 894 public static long getLongValue(String value, long defaultValue, String key) { 895 896 long result; 897 try { 898 result = Long.valueOf(value).longValue(); 899 } catch (Exception e) { 900 if (LOG.isDebugEnabled()) { 901 LOG.debug(Messages.get().getBundle().key(Messages.ERR_UNABLE_TO_PARSE_INT_2, value, key)); 902 } 903 result = defaultValue; 904 } 905 return result; 906 } 907 908 /** 909 * Splits a path into its non-empty path components.<p> 910 * 911 * If the path is the root path, an empty list will be returned.<p> 912 * 913 * @param path the path to split 914 * 915 * @return the list of non-empty path components 916 */ 917 public static List<String> getPathComponents(String path) { 918 919 List<String> result = CmsStringUtil.splitAsList(path, "/"); 920 Iterator<String> iter = result.iterator(); 921 while (iter.hasNext()) { 922 String token = iter.next(); 923 if (CmsStringUtil.isEmptyOrWhitespaceOnly(token)) { 924 iter.remove(); 925 } 926 } 927 return result; 928 } 929 930 /** 931 * Converts a 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.<p> 932 * 933 * @param base the base path 934 * @param path the path which should be converted to a relative path 935 * 936 * @return 'path' converted to a path relative to 'base', or null if 'path' is not a sub-folder of 'base' 937 */ 938 public static String getRelativeSubPath(String base, String path) { 939 940 String result = null; 941 base = CmsStringUtil.joinPaths(base, "/"); 942 path = CmsStringUtil.joinPaths(path, "/"); 943 if (path.startsWith(base)) { 944 result = path.substring(base.length()); 945 } 946 if (result != null) { 947 if (result.endsWith("/")) { 948 result = result.substring(0, result.length() - 1); 949 } 950 if (!result.startsWith("/")) { 951 result = "/" + result; 952 } 953 } 954 return result; 955 } 956 957 /** 958 * Returns <code>true</code> if the provided String is either <code>null</code> 959 * or the empty String <code>""</code>.<p> 960 * 961 * @param value the value to check 962 * 963 * @return true, if the provided value is null or the empty String, false otherwise 964 */ 965 public static boolean isEmpty(String value) { 966 967 return (value == null) || (value.length() == 0); 968 } 969 970 /** 971 * Returns <code>true</code> if the provided String is either <code>null</code> 972 * or contains only white spaces.<p> 973 * 974 * @param value the value to check 975 * 976 * @return true, if the provided value is null or contains only white spaces, false otherwise 977 */ 978 public static boolean isEmptyOrWhitespaceOnly(String value) { 979 980 return isEmpty(value) || (value.trim().length() == 0); 981 } 982 983 /** 984 * Returns <code>true</code> if the provided Objects are either both <code>null</code> 985 * or equal according to {@link Object#equals(Object)}.<p> 986 * 987 * @param value1 the first object to compare 988 * @param value2 the second object to compare 989 * 990 * @return <code>true</code> if the provided Objects are either both <code>null</code> 991 * or equal according to {@link Object#equals(Object)} 992 */ 993 public static boolean isEqual(Object value1, Object value2) { 994 995 if (value1 == null) { 996 return (value2 == null); 997 } 998 return value1.equals(value2); 999 } 1000 1001 /** 1002 * Returns <code>true</code> if the provided String is neither <code>null</code> 1003 * nor the empty String <code>""</code>.<p> 1004 * 1005 * @param value the value to check 1006 * 1007 * @return true, if the provided value is not null and not the empty String, false otherwise 1008 */ 1009 public static boolean isNotEmpty(String value) { 1010 1011 return (value != null) && (value.length() != 0); 1012 } 1013 1014 /** 1015 * Returns <code>true</code> if the provided String is neither <code>null</code> 1016 * nor contains only white spaces.<p> 1017 * 1018 * @param value the value to check 1019 * 1020 * @return <code>true</code>, if the provided value is <code>null</code> 1021 * or contains only white spaces, <code>false</code> otherwise 1022 */ 1023 public static boolean isNotEmptyOrWhitespaceOnly(String value) { 1024 1025 return (value != null) && (value.trim().length() > 0); 1026 } 1027 1028 /** 1029 * Checks whether one path is a prefix path of another, i.e. its path components are 1030 * the initial path components of the second path.<p> 1031 * 1032 * It is not enough to just use {@link String#startsWith}, because we want /foo/bar to 1033 * be a prefix path of /foo/bar/baz, but not of /foo/bar42.<p> 1034 * 1035 * @param firstPath the first path 1036 * @param secondPath the second path 1037 * 1038 * @return true if the first path is a prefix path of the second path 1039 */ 1040 public static boolean isPrefixPath(String firstPath, String secondPath) { 1041 1042 firstPath = CmsStringUtil.joinPaths(firstPath, "/"); 1043 secondPath = CmsStringUtil.joinPaths(secondPath, "/"); 1044 return secondPath.startsWith(firstPath); 1045 } 1046 1047 /** 1048 * Checks if the given class name is a valid Java class name.<p> 1049 * 1050 * @param className the name to check 1051 * 1052 * @return true if the given class name is a valid Java class name 1053 */ 1054 public static boolean isValidJavaClassName(String className) { 1055 1056 if (CmsStringUtil.isEmpty(className)) { 1057 return false; 1058 } 1059 int length = className.length(); 1060 boolean nodot = true; 1061 for (int i = 0; i < length; i++) { 1062 char ch = className.charAt(i); 1063 if (nodot) { 1064 if (ch == '.') { 1065 return false; 1066 } else if (Character.isJavaIdentifierStart(ch)) { 1067 nodot = false; 1068 } else { 1069 return false; 1070 } 1071 } else { 1072 if (ch == '.') { 1073 nodot = true; 1074 } else if (Character.isJavaIdentifierPart(ch)) { 1075 nodot = false; 1076 } else { 1077 return false; 1078 } 1079 } 1080 } 1081 return true; 1082 } 1083 1084 /** 1085 * Concatenates multiple paths and separates them with '/'.<p> 1086 * 1087 * Consecutive slashes will be reduced to a single slash in the resulting string. 1088 * For example, joinPaths("/foo/", "/bar", "baz") will return "/foo/bar/baz". 1089 * 1090 * @param paths the list of paths 1091 * 1092 * @return the joined path 1093 */ 1094 public static String joinPaths(List<String> paths) { 1095 1096 String result = listAsString(paths, "/"); 1097 // result may now contain multiple consecutive slashes, so reduce them to single slashes 1098 result = result.replaceAll("/+", "/"); 1099 return result; 1100 } 1101 1102 /** 1103 * Concatenates multiple paths and separates them with '/'.<p> 1104 * 1105 * Consecutive slashes will be reduced to a single slash in the resulting string. 1106 * For example, joinPaths("/foo/", "/bar", "baz") will return "/foo/bar/baz". 1107 * 1108 * @param paths the array of paths 1109 * 1110 * @return the joined path 1111 */ 1112 public static String joinPaths(String... paths) { 1113 1114 return joinPaths(Arrays.asList(paths)); 1115 } 1116 1117 /** 1118 * Returns the last index of any of the given chars in the given source.<p> 1119 * 1120 * If no char is found, -1 is returned.<p> 1121 * 1122 * @param source the source to check 1123 * @param chars the chars to find 1124 * 1125 * @return the last index of any of the given chars in the given source, or -1 1126 */ 1127 public static int lastIndexOf(String source, char[] chars) { 1128 1129 // now try to find an "sentence ending" char in the text in the "findPointArea" 1130 int result = -1; 1131 for (int i = 0; i < chars.length; i++) { 1132 int pos = source.lastIndexOf(chars[i]); 1133 if (pos > result) { 1134 // found new last char 1135 result = pos; 1136 } 1137 } 1138 return result; 1139 } 1140 1141 /** 1142 * Returns the last index a whitespace char the given source.<p> 1143 * 1144 * If no whitespace char is found, -1 is returned.<p> 1145 * 1146 * @param source the source to check 1147 * 1148 * @return the last index a whitespace char the given source, or -1 1149 */ 1150 public static int lastWhitespaceIn(String source) { 1151 1152 if (CmsStringUtil.isEmpty(source)) { 1153 return -1; 1154 } 1155 int pos = -1; 1156 for (int i = source.length() - 1; i >= 0; i--) { 1157 if (Character.isWhitespace(source.charAt(i))) { 1158 pos = i; 1159 break; 1160 } 1161 } 1162 return pos; 1163 } 1164 1165 /** 1166 * Returns a string representation for the given list using the given separator.<p> 1167 * 1168 * @param list the list to write 1169 * @param separator the item separator string 1170 * 1171 * @return the string representation for the given map 1172 */ 1173 public static String listAsString(List<?> list, String separator) { 1174 1175 StringBuffer string = new StringBuffer(128); 1176 Iterator<?> it = list.iterator(); 1177 while (it.hasNext()) { 1178 string.append(it.next()); 1179 if (it.hasNext()) { 1180 string.append(separator); 1181 } 1182 } 1183 return string.toString(); 1184 } 1185 1186 /** 1187 * Returns a string representation for the given map using the given separators.<p> 1188 * 1189 * @param <K> type of map keys 1190 * @param <V> type of map values 1191 * @param map the map to write 1192 * @param sepItem the item separator string 1193 * @param sepKeyval the key-value pair separator string 1194 * 1195 * @return the string representation for the given map 1196 */ 1197 public static <K, V> String mapAsString(Map<K, V> map, String sepItem, String sepKeyval) { 1198 1199 StringBuffer string = new StringBuffer(128); 1200 Iterator<Map.Entry<K, V>> it = map.entrySet().iterator(); 1201 while (it.hasNext()) { 1202 Map.Entry<K, V> entry = it.next(); 1203 string.append(entry.getKey()); 1204 string.append(sepKeyval); 1205 string.append(entry.getValue()); 1206 if (it.hasNext()) { 1207 string.append(sepItem); 1208 } 1209 } 1210 return string.toString(); 1211 } 1212 1213 /** 1214 * Applies white space padding to the left of the given String.<p> 1215 * 1216 * @param input the input to pad left 1217 * @param size the size of the padding 1218 * 1219 * @return the input padded to the left 1220 */ 1221 public static String padLeft(String input, int size) { 1222 1223 return (new PrintfFormat("%" + size + "s")).sprintf(input); 1224 } 1225 1226 /** 1227 * Applies white space padding to the right of the given String.<p> 1228 * 1229 * @param input the input to pad right 1230 * @param size the size of the padding 1231 * 1232 * @return the input padded to the right 1233 */ 1234 public static String padRight(String input, int size) { 1235 1236 return (new PrintfFormat("%-" + size + "s")).sprintf(input); 1237 } 1238 1239 /** 1240 * Splits a String into substrings along the provided char delimiter and returns 1241 * the result as an Array of Substrings.<p> 1242 * 1243 * @param source the String to split 1244 * @param delimiter the delimiter to split at 1245 * 1246 * @return the Array of splitted Substrings 1247 */ 1248 public static String[] splitAsArray(String source, char delimiter) { 1249 1250 List<String> result = splitAsList(source, delimiter); 1251 return result.toArray(new String[result.size()]); 1252 } 1253 1254 /** 1255 * Splits a String into substrings along the provided String delimiter and returns 1256 * the result as an Array of Substrings.<p> 1257 * 1258 * @param source the String to split 1259 * @param delimiter the delimiter to split at 1260 * 1261 * @return the Array of splitted Substrings 1262 */ 1263 public static String[] splitAsArray(String source, String delimiter) { 1264 1265 List<String> result = splitAsList(source, delimiter); 1266 return result.toArray(new String[result.size()]); 1267 } 1268 1269 /** 1270 * Splits a String into substrings along the provided char delimiter and returns 1271 * the result as a List of Substrings.<p> 1272 * 1273 * @param source the String to split 1274 * @param delimiter the delimiter to split at 1275 * 1276 * @return the List of splitted Substrings 1277 */ 1278 public static List<String> splitAsList(String source, char delimiter) { 1279 1280 return splitAsList(source, delimiter, false); 1281 } 1282 1283 /** 1284 * Splits a String into substrings along the provided char delimiter and returns 1285 * the result as a List of Substrings.<p> 1286 * 1287 * @param source the String to split 1288 * @param delimiter the delimiter to split at 1289 * @param trim flag to indicate if leading and trailing white spaces should be omitted 1290 * 1291 * @return the List of splitted Substrings 1292 */ 1293 public static List<String> splitAsList(String source, char delimiter, boolean trim) { 1294 1295 List<String> result = new ArrayList<String>(); 1296 int i = 0; 1297 int l = source.length(); 1298 int n = source.indexOf(delimiter); 1299 while (n != -1) { 1300 // zero - length items are not seen as tokens at start or end 1301 if ((i < n) || ((i > 0) && (i < l))) { 1302 result.add(trim ? source.substring(i, n).trim() : source.substring(i, n)); 1303 } 1304 i = n + 1; 1305 n = source.indexOf(delimiter, i); 1306 } 1307 // is there a non - empty String to cut from the tail? 1308 if (n < 0) { 1309 n = source.length(); 1310 } 1311 if (i < n) { 1312 result.add(trim ? source.substring(i).trim() : source.substring(i)); 1313 } 1314 return result; 1315 } 1316 1317 /** 1318 * Splits a String into substrings along the provided String delimiter and returns 1319 * the result as List of Substrings.<p> 1320 * 1321 * @param source the String to split 1322 * @param delimiter the delimiter to split at 1323 * 1324 * @return the Array of splitted Substrings 1325 */ 1326 public static List<String> splitAsList(String source, String delimiter) { 1327 1328 return splitAsList(source, delimiter, false); 1329 } 1330 1331 /** 1332 * Splits a String into substrings along the provided String delimiter and returns 1333 * the result as List of Substrings.<p> 1334 * 1335 * @param source the String to split 1336 * @param delimiter the delimiter to split at 1337 * @param trim flag to indicate if leading and trailing white spaces should be omitted 1338 * 1339 * @return the Array of splitted Substrings 1340 */ 1341 public static List<String> splitAsList(String source, String delimiter, boolean trim) { 1342 1343 int dl = delimiter.length(); 1344 if (dl == 1) { 1345 // optimize for short strings 1346 return splitAsList(source, delimiter.charAt(0), trim); 1347 } 1348 1349 List<String> result = new ArrayList<String>(); 1350 int i = 0; 1351 int l = source.length(); 1352 int n = source.indexOf(delimiter); 1353 while (n != -1) { 1354 // zero - length items are not seen as tokens at start or end: ",," is one empty token but not three 1355 if ((i < n) || ((i > 0) && (i < l))) { 1356 result.add(trim ? source.substring(i, n).trim() : source.substring(i, n)); 1357 } 1358 i = n + dl; 1359 n = source.indexOf(delimiter, i); 1360 } 1361 // is there a non - empty String to cut from the tail? 1362 if (n < 0) { 1363 n = source.length(); 1364 } 1365 if (i < n) { 1366 result.add(trim ? source.substring(i).trim() : source.substring(i)); 1367 } 1368 return result; 1369 } 1370 1371 /** 1372 * Splits a String into substrings along the provided <code>paramDelim</code> delimiter, 1373 * then each substring is treat as a key-value pair delimited by <code>keyValDelim</code>.<p> 1374 * 1375 * @param source the string to split 1376 * @param paramDelim the string to delimit each key-value pair 1377 * @param keyValDelim the string to delimit key and value 1378 * 1379 * @return a map of splitted key-value pairs 1380 */ 1381 public static Map<String, String> splitAsMap(String source, String paramDelim, String keyValDelim) { 1382 1383 int keyValLen = keyValDelim.length(); 1384 // use LinkedHashMap to preserve the order of items 1385 Map<String, String> params = new LinkedHashMap<String, String>(); 1386 Iterator<String> itParams = CmsStringUtil.splitAsList(source, paramDelim, true).iterator(); 1387 while (itParams.hasNext()) { 1388 String param = itParams.next(); 1389 int pos = param.indexOf(keyValDelim); 1390 String key = param; 1391 String value = ""; 1392 if (pos > 0) { 1393 key = param.substring(0, pos); 1394 if ((pos + keyValLen) < param.length()) { 1395 value = param.substring(pos + keyValLen); 1396 } 1397 } 1398 params.put(key, value); 1399 } 1400 return params; 1401 } 1402 1403 /** 1404 * Substitutes a pattern in a string using a {@link I_CmsRegexSubstitution}.<p> 1405 * 1406 * @param pattern the pattern to substitute 1407 * @param text the text in which the pattern should be substituted 1408 * @param sub the substitution handler 1409 * 1410 * @return the transformed string 1411 */ 1412 public static String substitute(Pattern pattern, String text, I_CmsRegexSubstitution sub) { 1413 1414 StringBuffer buffer = new StringBuffer(); 1415 Matcher matcher = pattern.matcher(text); 1416 while (matcher.find()) { 1417 matcher.appendReplacement(buffer, sub.substituteMatch(text, matcher)); 1418 } 1419 matcher.appendTail(buffer); 1420 return buffer.toString(); 1421 } 1422 1423 /** 1424 * Replaces a set of <code>searchString</code> and <code>replaceString</code> pairs, 1425 * given by the <code>substitutions</code> Map parameter.<p> 1426 * 1427 * @param source the string to scan 1428 * @param substitions the map of substitutions 1429 * 1430 * @return the substituted String 1431 * 1432 * @see #substitute(String, String, String) 1433 */ 1434 public static String substitute(String source, Map<String, String> substitions) { 1435 1436 String result = source; 1437 Iterator<Map.Entry<String, String>> it = substitions.entrySet().iterator(); 1438 while (it.hasNext()) { 1439 Map.Entry<String, String> entry = it.next(); 1440 result = substitute(result, entry.getKey(), entry.getValue().toString()); 1441 } 1442 return result; 1443 } 1444 1445 /** 1446 * Substitutes <code>searchString</code> in the given source String with <code>replaceString</code>.<p> 1447 * 1448 * This is a high-performance implementation which should be used as a replacement for 1449 * <code>{@link String#replaceAll(java.lang.String, java.lang.String)}</code> in case no 1450 * regular expression evaluation is required.<p> 1451 * 1452 * @param source the content which is scanned 1453 * @param searchString the String which is searched in content 1454 * @param replaceString the String which replaces <code>searchString</code> 1455 * 1456 * @return the substituted String 1457 */ 1458 public static String substitute(String source, String searchString, String replaceString) { 1459 1460 if (source == null) { 1461 return null; 1462 } 1463 1464 if (isEmpty(searchString)) { 1465 return source; 1466 } 1467 1468 if (replaceString == null) { 1469 replaceString = ""; 1470 } 1471 int len = source.length(); 1472 int sl = searchString.length(); 1473 int rl = replaceString.length(); 1474 int length; 1475 if (sl == rl) { 1476 length = len; 1477 } else { 1478 int c = 0; 1479 int s = 0; 1480 int e; 1481 while ((e = source.indexOf(searchString, s)) != -1) { 1482 c++; 1483 s = e + sl; 1484 } 1485 if (c == 0) { 1486 return source; 1487 } 1488 length = len - (c * (sl - rl)); 1489 } 1490 1491 int s = 0; 1492 int e = source.indexOf(searchString, s); 1493 if (e == -1) { 1494 return source; 1495 } 1496 StringBuffer sb = new StringBuffer(length); 1497 while (e != -1) { 1498 sb.append(source.substring(s, e)); 1499 sb.append(replaceString); 1500 s = e + sl; 1501 e = source.indexOf(searchString, s); 1502 } 1503 e = len; 1504 sb.append(source.substring(s, e)); 1505 return sb.toString(); 1506 } 1507 1508 /** 1509 * Substitutes the OpenCms context path (e.g. /opencms/opencms/) in a HTML page with a 1510 * special variable so that the content also runs if the context path of the server changes.<p> 1511 * 1512 * @param htmlContent the HTML to replace the context path in 1513 * @param context the context path of the server 1514 * 1515 * @return the HTML with the replaced context path 1516 */ 1517 public static String substituteContextPath(String htmlContent, String context) { 1518 1519 if (m_contextSearch == null) { 1520 m_contextSearch = "([^\\w/])" + context; 1521 m_contextReplace = "$1" + CmsStringUtil.escapePattern(CmsStringUtil.MACRO_OPENCMS_CONTEXT) + "/"; 1522 } 1523 return substitutePerl(htmlContent, m_contextSearch, m_contextReplace, "g"); 1524 } 1525 1526 /** 1527 * Substitutes searchString in content with replaceItem.<p> 1528 * 1529 * @param content the content which is scanned 1530 * @param searchString the String which is searched in content 1531 * @param replaceItem the new String which replaces searchString 1532 * @param occurences must be a "g" if all occurrences of searchString shall be replaced 1533 * 1534 * @return String the substituted String 1535 */ 1536 public static String substitutePerl(String content, String searchString, String replaceItem, String occurences) { 1537 1538 String translationRule = "s#" + searchString + "#" + replaceItem + "#" + occurences; 1539 Perl5Util perlUtil = new Perl5Util(); 1540 try { 1541 return perlUtil.substitute(translationRule, content); 1542 } catch (MalformedPerl5PatternException e) { 1543 if (LOG.isDebugEnabled()) { 1544 LOG.debug(Messages.get().getBundle().key(Messages.LOG_MALFORMED_TRANSLATION_RULE_1, translationRule), e); 1545 } 1546 } 1547 return content; 1548 } 1549 1550 /** 1551 * Returns the java String literal for the given String. <p> 1552 * 1553 * This is the form of the String that had to be written into source code 1554 * using the unicode escape sequence for special characters. <p> 1555 * 1556 * Example: "Ä" would be transformed to "\\u00C4".<p> 1557 * 1558 * @param s a string that may contain non-ascii characters 1559 * 1560 * @return the java unicode escaped string Literal of the given input string 1561 */ 1562 public static String toUnicodeLiteral(String s) { 1563 1564 StringBuffer result = new StringBuffer(); 1565 char[] carr = s.toCharArray(); 1566 1567 String unicode; 1568 for (int i = 0; i < carr.length; i++) { 1569 result.append("\\u"); 1570 // append leading zeros 1571 unicode = Integer.toHexString(carr[i]).toUpperCase(); 1572 for (int j = 4 - unicode.length(); j > 0; j--) { 1573 result.append("0"); 1574 } 1575 result.append(unicode); 1576 } 1577 return result.toString(); 1578 } 1579 1580 /** 1581 * This method transformes a string which matched a format with one or more place holders into another format. The 1582 * other format also includes the same number of place holders. Place holders start with 1583 * {@link org.opencms.util.CmsStringUtil#PLACEHOLDER_START} and end with {@link org.opencms.util.CmsStringUtil#PLACEHOLDER_END}.<p> 1584 * 1585 * @param oldFormat the original format 1586 * @param newFormat the new format 1587 * @param value the value which matched the original format and which shall be transformed into the new format 1588 * 1589 * @return the new value with the filled place holder with the information in the parameter value 1590 */ 1591 public static String transformValues(String oldFormat, String newFormat, String value) { 1592 1593 if (!oldFormat.contains(CmsStringUtil.PLACEHOLDER_START) 1594 || !oldFormat.contains(CmsStringUtil.PLACEHOLDER_END) 1595 || !newFormat.contains(CmsStringUtil.PLACEHOLDER_START) 1596 || !newFormat.contains(CmsStringUtil.PLACEHOLDER_END)) { 1597 // no place holders are set in correct format 1598 // that is why there is nothing to calculate and the value is the new format 1599 return newFormat; 1600 } 1601 //initialize the arrays with the values where the place holders starts 1602 ArrayList<Integer> oldValues = new ArrayList<Integer>(); 1603 ArrayList<Integer> newValues = new ArrayList<Integer>(); 1604 1605 // count the number of placeholders 1606 // for example these are three pairs: 1607 // old format: {.*}<b>{.*}</b>{.*} 1608 // new format: {}<strong>{}</strong>{} 1609 // get the number of place holders in the old format 1610 int oldNumber = 0; 1611 try { 1612 int counter = 0; 1613 Pattern pattern = Pattern.compile("\\{\\.\\*\\}"); 1614 Matcher matcher = pattern.matcher(oldFormat); 1615 // get the number of matches 1616 while (matcher.find()) { 1617 counter += 1; 1618 } 1619 oldValues = new ArrayList<Integer>(counter); 1620 matcher = pattern.matcher(oldFormat); 1621 while (matcher.find()) { 1622 int start = matcher.start() + 1; 1623 oldValues.add(oldNumber, new Integer(start)); 1624 oldNumber += 1; 1625 } 1626 } catch (PatternSyntaxException e) { 1627 // do nothing 1628 } 1629 // get the number of place holders in the new format 1630 int newNumber = 0; 1631 try { 1632 int counter = 0; 1633 Pattern pattern = Pattern.compile("\\{\\}"); 1634 Matcher matcher = pattern.matcher(newFormat); 1635 // get the number of matches 1636 while (matcher.find()) { 1637 counter += 1; 1638 } 1639 newValues = new ArrayList<Integer>(counter); 1640 matcher = pattern.matcher(newFormat); 1641 while (matcher.find()) { 1642 int start = matcher.start() + 1; 1643 newValues.add(newNumber, new Integer(start)); 1644 newNumber += 1; 1645 } 1646 } catch (PatternSyntaxException e) { 1647 // do nothing 1648 } 1649 // prove the numbers of place holders 1650 if (oldNumber != newNumber) { 1651 // not the same number of place holders in the old and in the new format 1652 return newFormat; 1653 } 1654 1655 // initialize the arrays with the values between the place holders 1656 ArrayList<String> oldBetween = new ArrayList<String>(oldNumber + 1); 1657 ArrayList<String> newBetween = new ArrayList<String>(newNumber + 1); 1658 1659 // get the values between the place holders for the old format 1660 // for this example with oldFormat: {.*}<b>{.*}</b>{.*} 1661 // this array is that: 1662 // --------- 1663 // | empty | 1664 // --------- 1665 // | <b> | 1666 // |-------- 1667 // | </b> | 1668 // |-------- 1669 // | empty | 1670 // |-------- 1671 int counter = 0; 1672 Iterator<Integer> iter = oldValues.iterator(); 1673 while (iter.hasNext()) { 1674 int start = iter.next().intValue(); 1675 if (counter == 0) { 1676 // the first entry 1677 if (start == 1) { 1678 // the first place holder starts at the beginning of the old format 1679 // for example: {.*}<b>... 1680 oldBetween.add(counter, ""); 1681 } else { 1682 // the first place holder starts NOT at the beginning of the old format 1683 // for example: <a>{.*}<b>... 1684 String part = oldFormat.substring(0, start - 1); 1685 oldBetween.add(counter, part); 1686 } 1687 } else { 1688 // the entries between the first and the last entry 1689 int lastStart = oldValues.get(counter - 1).intValue(); 1690 String part = oldFormat.substring(lastStart + 3, start - 1); 1691 oldBetween.add(counter, part); 1692 } 1693 counter += 1; 1694 } 1695 // the last element 1696 int lastElstart = oldValues.get(counter - 1).intValue(); 1697 if ((lastElstart + 2) == (oldFormat.length() - 1)) { 1698 // the last place holder ends at the end of the old format 1699 // for example: ...</b>{.*} 1700 oldBetween.add(counter, ""); 1701 } else { 1702 // the last place holder ends NOT at the end of the old format 1703 // for example: ...</b>{.*}</a> 1704 String part = oldFormat.substring(lastElstart + 3); 1705 oldBetween.add(counter, part); 1706 } 1707 1708 // get the values between the place holders for the new format 1709 // for this example with newFormat: {}<strong>{}</strong>{} 1710 // this array is that: 1711 // ------------| 1712 // | empty | 1713 // ------------| 1714 // | <strong> | 1715 // |-----------| 1716 // | </strong> | 1717 // |-----------| 1718 // | empty | 1719 // |-----------| 1720 counter = 0; 1721 iter = newValues.iterator(); 1722 while (iter.hasNext()) { 1723 int start = iter.next().intValue(); 1724 if (counter == 0) { 1725 // the first entry 1726 if (start == 1) { 1727 // the first place holder starts at the beginning of the new format 1728 // for example: {.*}<b>... 1729 newBetween.add(counter, ""); 1730 } else { 1731 // the first place holder starts NOT at the beginning of the new format 1732 // for example: <a>{.*}<b>... 1733 String part = newFormat.substring(0, start - 1); 1734 newBetween.add(counter, part); 1735 } 1736 } else { 1737 // the entries between the first and the last entry 1738 int lastStart = newValues.get(counter - 1).intValue(); 1739 String part = newFormat.substring(lastStart + 1, start - 1); 1740 newBetween.add(counter, part); 1741 } 1742 counter += 1; 1743 } 1744 // the last element 1745 lastElstart = newValues.get(counter - 1).intValue(); 1746 if ((lastElstart + 2) == (newFormat.length() - 1)) { 1747 // the last place holder ends at the end of the old format 1748 // for example: ...</b>{.*} 1749 newBetween.add(counter, ""); 1750 } else { 1751 // the last place holder ends NOT at the end of the old format 1752 // for example: ...</b>{.*}</a> 1753 String part = newFormat.substring(lastElstart + 1); 1754 newBetween.add(counter, part); 1755 } 1756 1757 // get the values in the place holders 1758 // for the example with: 1759 // oldFormat: {.*}<b>{.*}</b>{.*} 1760 // newFormat: {}<strong>{}</strong>{} 1761 // value: abc<b>def</b>ghi 1762 // it is used the array with the old values between the place holders to get the content in the place holders 1763 // this result array is that: 1764 // ------| 1765 // | abc | 1766 // ------| 1767 // | def | 1768 // |-----| 1769 // | ghi | 1770 // |-----| 1771 ArrayList<String> placeHolders = new ArrayList<String>(oldNumber); 1772 String tmpValue = value; 1773 // loop over all rows with the old values between the place holders and take the values between them in the 1774 // current property value 1775 for (int placeCounter = 0; placeCounter < (oldBetween.size() - 1); placeCounter++) { 1776 // get the two next values with the old values between the place holders 1777 String content = oldBetween.get(placeCounter); 1778 String nextContent = oldBetween.get(placeCounter + 1); 1779 // check the position of the first of the next values in the current property value 1780 int contPos = 0; 1781 int nextContPos = 0; 1782 if ((placeCounter == 0) && CmsStringUtil.isEmpty(content)) { 1783 // the first value in the values between the place holders is empty 1784 // for example: {.*}<p>... 1785 contPos = 0; 1786 } else { 1787 // the first value in the values between the place holders is NOT empty 1788 // for example: bla{.*}<p>... 1789 contPos = tmpValue.indexOf(content); 1790 } 1791 // check the position of the second of the next values in the current property value 1792 if (((placeCounter + 1) == (oldBetween.size() - 1)) && CmsStringUtil.isEmpty(nextContent)) { 1793 // the last value in the values between the place holders is empty 1794 // for example: ...<p>{.*} 1795 nextContPos = tmpValue.length(); 1796 } else { 1797 // the last value in the values between the place holders is NOT empty 1798 // for example: ...<p>{.*}bla 1799 nextContPos = tmpValue.indexOf(nextContent); 1800 } 1801 // every value must match the current value 1802 if ((contPos < 0) || (nextContPos < 0)) { 1803 return value; 1804 } 1805 // get the content of the current place holder 1806 String placeContent = tmpValue.substring(contPos + content.length(), nextContPos); 1807 placeHolders.add(placeCounter, placeContent); 1808 // cut off the currently visited part of the value 1809 tmpValue = tmpValue.substring(nextContPos); 1810 } 1811 1812 // build the new format 1813 // with following vectors from above: 1814 // old values between the place holders: 1815 // --------- 1816 // | empty | (old.1) 1817 // --------- 1818 // | <b> | (old.2) 1819 // |-------- 1820 // | </b> | (old.3) 1821 // |-------- 1822 // | empty | (old.4) 1823 // |-------- 1824 // 1825 // new values between the place holders: 1826 // ------------| 1827 // | empty | (new.1) 1828 // ------------| 1829 // | <strong> | (new.2) 1830 // |-----------| 1831 // | </strong> | (new.3) 1832 // |-----------| 1833 // | empty | (new.4) 1834 // |-----------| 1835 // 1836 // content of the place holders: 1837 // ------| 1838 // | abc | (place.1) 1839 // ------| 1840 // | def | (place.2) 1841 // |-----| 1842 // | ghi | (place.3) 1843 // |-----| 1844 // 1845 // the result is calculated in that way: 1846 // new.1 + place.1 + new.2 + place.2 + new.3 + place.3 + new.4 1847 String newValue = ""; 1848 // take the values between the place holders and add the content of the place holders 1849 for (int buildCounter = 0; buildCounter < newNumber; buildCounter++) { 1850 newValue = newValue + newBetween.get(buildCounter) + placeHolders.get(buildCounter); 1851 } 1852 newValue = newValue + newBetween.get(newNumber); 1853 // return the changed value 1854 return newValue; 1855 } 1856 1857 /** 1858 * Returns a substring of the source, which is at most length characters long.<p> 1859 * 1860 * This is the same as calling {@link #trimToSize(String, int, String)} with the 1861 * parameters <code>(source, length, " ...")</code>.<p> 1862 * 1863 * @param source the string to trim 1864 * @param length the maximum length of the string to be returned 1865 * 1866 * @return a substring of the source, which is at most length characters long 1867 */ 1868 public static String trimToSize(String source, int length) { 1869 1870 return trimToSize(source, length, length, " ..."); 1871 } 1872 1873 /** 1874 * Returns a substring of the source, which is at most length characters long, cut 1875 * in the last <code>area</code> chars in the source at a sentence ending char or whitespace.<p> 1876 * 1877 * If a char is cut, the given <code>suffix</code> is appended to the result.<p> 1878 * 1879 * @param source the string to trim 1880 * @param length the maximum length of the string to be returned 1881 * @param area the area at the end of the string in which to find a sentence ender or whitespace 1882 * @param suffix the suffix to append in case the String was trimmed 1883 * 1884 * @return a substring of the source, which is at most length characters long 1885 */ 1886 public static String trimToSize(String source, int length, int area, String suffix) { 1887 1888 if ((source == null) || (source.length() <= length)) { 1889 // no operation is required 1890 return source; 1891 } 1892 if (CmsStringUtil.isEmpty(suffix)) { 1893 // we need an empty suffix 1894 suffix = ""; 1895 } 1896 // must remove the length from the after sequence chars since these are always added in the end 1897 int modLength = length - suffix.length(); 1898 if (modLength <= 0) { 1899 // we are to short, return beginning of the suffix 1900 return suffix.substring(0, length); 1901 } 1902 int modArea = area + suffix.length(); 1903 if ((modArea > modLength) || (modArea < 0)) { 1904 // area must not be longer then max length 1905 modArea = modLength; 1906 } 1907 1908 // first reduce the String to the maximum allowed length 1909 String findPointSource = source.substring(modLength - modArea, modLength); 1910 1911 String result; 1912 // try to find an "sentence ending" char in the text 1913 int pos = lastIndexOf(findPointSource, SENTENCE_ENDING_CHARS); 1914 if (pos >= 0) { 1915 // found a sentence ender in the lookup area, keep the sentence ender 1916 result = source.substring(0, (modLength - modArea) + pos + 1) + suffix; 1917 } else { 1918 // no sentence ender was found, try to find a whitespace 1919 pos = lastWhitespaceIn(findPointSource); 1920 if (pos >= 0) { 1921 // found a whitespace, don't keep the whitespace 1922 result = source.substring(0, (modLength - modArea) + pos) + suffix; 1923 } else { 1924 // not even a whitespace was found, just cut away what's to long 1925 result = source.substring(0, modLength) + suffix; 1926 } 1927 } 1928 1929 return result; 1930 } 1931 1932 /** 1933 * Returns a substring of the source, which is at most length characters long.<p> 1934 * 1935 * If a char is cut, the given <code>suffix</code> is appended to the result.<p> 1936 * 1937 * This is almost the same as calling {@link #trimToSize(String, int, int, String)} with the 1938 * parameters <code>(source, length, length*, suffix)</code>. If <code>length</code> 1939 * if larger then 100, then <code>length* = length / 2</code>, 1940 * otherwise <code>length* = length</code>.<p> 1941 * 1942 * @param source the string to trim 1943 * @param length the maximum length of the string to be returned 1944 * @param suffix the suffix to append in case the String was trimmed 1945 * 1946 * @return a substring of the source, which is at most length characters long 1947 */ 1948 public static String trimToSize(String source, int length, String suffix) { 1949 1950 int area = (length > 100) ? length / 2 : length; 1951 return trimToSize(source, length, area, suffix); 1952 } 1953 1954 /** 1955 * Validates a value against a regular expression.<p> 1956 * 1957 * @param value the value to test 1958 * @param regex the regular expression 1959 * @param allowEmpty if an empty value is allowed 1960 * 1961 * @return <code>true</code> if the value satisfies the validation 1962 */ 1963 public static boolean validateRegex(String value, String regex, boolean allowEmpty) { 1964 1965 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) { 1966 return allowEmpty; 1967 } 1968 Pattern pattern = Pattern.compile(regex); 1969 Matcher matcher = pattern.matcher(value); 1970 return matcher.matches(); 1971 } 1972 1973}