001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/framework/util/StringTools.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.framework.util; 037 038 import java.io.BufferedReader; 039 import java.io.IOException; 040 import java.io.InputStream; 041 import java.io.InputStreamReader; 042 import java.io.PrintStream; 043 import java.io.StringReader; 044 import java.util.ArrayList; 045 import java.util.HashMap; 046 import java.util.Iterator; 047 import java.util.List; 048 import java.util.Locale; 049 import java.util.Map; 050 import java.util.Set; 051 import java.util.StringTokenizer; 052 053 import org.deegree.framework.xml.XMLFragment; 054 import org.deegree.framework.xml.XMLParsingException; 055 import org.deegree.framework.xml.XMLTools; 056 import org.deegree.ogcbase.CommonNamespaces; 057 import org.w3c.dom.Node; 058 import org.xml.sax.SAXException; 059 060 /** 061 * this is a collection of some methods that extends the functionality of the sun-java string class. 062 * 063 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 064 * @author last edited by: $Author: apoth $ 065 * 066 * @version $Revision: 29560 $, $Date: 2011-02-07 15:35:13 +0100 (Mo, 07 Feb 2011) $ 067 */ 068 public class StringTools { 069 070 /** 071 * This map is used for methods normalizeString() and initMap(). 072 * 073 * key = locale language, e.g. "de" value = map of substitution rules for this locale 074 */ 075 private static Map<String, Map<String, String>> localeMap; 076 077 /** 078 * concatenates an array of strings using a 079 * 080 * @see StringBuffer 081 * 082 * @param size 083 * estimated size of the target string 084 * @param objects 085 * toString() will be called for each object to append it to the result string 086 * @return the concatenated String 087 */ 088 public static String concat( int size, Object... objects ) { 089 StringBuilder sbb = new StringBuilder( size ); 090 for ( int i = 0; i < objects.length; i++ ) { 091 sbb.append( objects[i] ); 092 } 093 return sbb.toString(); 094 } 095 096 /** 097 * replaces occurences of a string fragment within a string by a new string. 098 * 099 * @param target 100 * is the original string 101 * @param from 102 * is the string to be replaced 103 * @param to 104 * is the string which will used to replace 105 * @param all 106 * if it's true all occurences of the string to be replaced will be replaced. else only the first 107 * occurence will be replaced. 108 * @return the changed target string 109 */ 110 public static String replace( String target, String from, String to, boolean all ) { 111 112 StringBuffer buffer = new StringBuffer( target.length() ); 113 int copyFrom = 0; 114 char[] targetChars = null; 115 int lf = from.length(); 116 int start = -1; 117 do { 118 start = target.indexOf( from ); 119 copyFrom = 0; 120 if ( start == -1 ) { 121 return target; 122 } 123 124 targetChars = target.toCharArray(); 125 while ( start != -1 ) { 126 buffer.append( targetChars, copyFrom, start - copyFrom ); 127 buffer.append( to ); 128 copyFrom = start + lf; 129 start = target.indexOf( from, copyFrom ); 130 if ( !all ) { 131 start = -1; 132 } 133 } 134 buffer.append( targetChars, copyFrom, targetChars.length - copyFrom ); 135 target = buffer.toString(); 136 buffer.delete( 0, buffer.length() ); 137 } while ( target.indexOf( from ) > -1 && to.indexOf( from ) < 0 ); 138 139 return target; 140 } 141 142 /** 143 * parse a string and return its tokens as array 144 * 145 * @param s 146 * string to parse 147 * @param delimiter 148 * delimiter that marks the end of a token 149 * @param deleteDoubles 150 * if it's true all string that are already within the resulting array will be deleted, so that there 151 * will only be one copy of them. 152 * @return an Array of Strings 153 */ 154 public static String[] toArray( String s, String delimiter, boolean deleteDoubles ) { 155 if ( s == null || s.equals( "" ) ) { 156 return new String[0]; 157 } 158 159 StringTokenizer st = new StringTokenizer( s, delimiter ); 160 ArrayList<String> vec = new ArrayList<String>( st.countTokens() ); 161 162 if ( st.countTokens() > 0 ) { 163 for ( int i = 0; st.hasMoreTokens(); i++ ) { 164 String t = st.nextToken(); 165 if ( ( t != null ) && ( t.length() > 0 ) ) { 166 vec.add( t.trim() ); 167 } 168 } 169 } else { 170 vec.add( s ); 171 } 172 173 String[] kw = vec.toArray( new String[vec.size()] ); 174 if ( deleteDoubles ) { 175 kw = deleteDoubles( kw ); 176 } 177 178 return kw; 179 } 180 181 /** 182 * parse a string and return its tokens as typed List. empty fields will be removed from the list. 183 * 184 * @param s 185 * string to parse 186 * @param delimiter 187 * delimiter that marks the end of a token 188 * @param deleteDoubles 189 * if it's true all string that are already within the resulting array will be deleted, so that there 190 * will only be one copy of them. 191 * @return a list of Strings 192 */ 193 public static List<String> toList( String s, String delimiter, boolean deleteDoubles ) { 194 if ( s == null || s.equals( "" ) ) { 195 return new ArrayList<String>(); 196 } 197 198 StringTokenizer st = new StringTokenizer( s, delimiter ); 199 ArrayList<String> vec = new ArrayList<String>( st.countTokens() ); 200 for ( int i = 0; st.hasMoreTokens(); i++ ) { 201 String t = st.nextToken(); 202 if ( ( t != null ) && ( t.length() > 0 ) ) { 203 if ( deleteDoubles ) { 204 if ( !vec.contains( t.trim() ) ) { 205 vec.add( t.trim() ); 206 } 207 } else { 208 vec.add( t.trim() ); 209 } 210 } 211 } 212 213 return vec; 214 } 215 216 /** 217 * transforms a string array to one string. the array fields are separated by the submitted delimiter: 218 * 219 * @param s 220 * stringarray to transform 221 * @param delimiter 222 * @return the String representation of the given array 223 */ 224 public static String arrayToString( String[] s, char delimiter ) { 225 StringBuffer res = new StringBuffer( s.length * 20 ); 226 227 for ( int i = 0; i < s.length; i++ ) { 228 res.append( s[i] ); 229 230 if ( i < ( s.length - 1 ) ) { 231 res.append( delimiter ); 232 } 233 } 234 235 return res.toString(); 236 } 237 238 /** 239 * transforms a list to one string. the array fields are separated by the submitted delimiter: 240 * 241 * @param s 242 * stringarray to transform 243 * @param delimiter 244 * @return the String representation of the given list. 245 */ 246 public static String listToString( List<?> s, char delimiter ) { 247 StringBuffer res = new StringBuffer( s.size() * 20 ); 248 249 for ( int i = 0; i < s.size(); i++ ) { 250 res.append( s.get( i ) ); 251 252 if ( i < ( s.size() - 1 ) ) { 253 res.append( delimiter ); 254 } 255 } 256 257 return res.toString(); 258 } 259 260 /** 261 * transforms a double array to one string. the array fields are separated by the submitted delimiter: 262 * 263 * @param s 264 * string array to transform 265 * @param delimiter 266 * @return the String representation of the given array 267 */ 268 public static String arrayToString( double[] s, char delimiter ) { 269 StringBuffer res = new StringBuffer( s.length * 20 ); 270 271 for ( int i = 0; i < s.length; i++ ) { 272 res.append( Double.toString( s[i] ) ); 273 274 if ( i < ( s.length - 1 ) ) { 275 res.append( delimiter ); 276 } 277 } 278 279 return res.toString(); 280 } 281 282 /** 283 * transforms a float array to one string. the array fields are separated by the submitted delimiter: 284 * 285 * @param s 286 * float array to transform 287 * @param delimiter 288 * @return the String representation of the given array 289 */ 290 public static String arrayToString( float[] s, char delimiter ) { 291 StringBuffer res = new StringBuffer( s.length * 20 ); 292 293 for ( int i = 0; i < s.length; i++ ) { 294 res.append( Float.toString( s[i] ) ); 295 296 if ( i < ( s.length - 1 ) ) { 297 res.append( delimiter ); 298 } 299 } 300 301 return res.toString(); 302 } 303 304 /** 305 * transforms a int array to one string. the array fields are separated by the submitted delimiter: 306 * 307 * @param s 308 * stringarray to transform 309 * @param delimiter 310 * @return the String representation of the given array 311 */ 312 public static String arrayToString( int[] s, char delimiter ) { 313 StringBuffer res = new StringBuffer( s.length * 20 ); 314 315 for ( int i = 0; i < s.length; i++ ) { 316 res.append( Integer.toString( s[i] ) ); 317 318 if ( i < ( s.length - 1 ) ) { 319 res.append( delimiter ); 320 } 321 } 322 323 return res.toString(); 324 } 325 326 /** 327 * clears the begin and end of a string from the strings submitted 328 * 329 * @param s 330 * string to validate 331 * @param mark 332 * string to remove from begin and end of <code>s</code> 333 * @return the substring of the given String without the mark at the and and the begin, and trimmed 334 */ 335 public static String validateString( String s, String mark ) { 336 if ( s == null ) { 337 return null; 338 } 339 340 if ( s.length() == 0 ) { 341 return s; 342 } 343 344 s = s.trim(); 345 346 while ( s.startsWith( mark ) ) { 347 s = s.substring( mark.length(), s.length() ).trim(); 348 } 349 350 while ( s.endsWith( mark ) ) { 351 s = s.substring( 0, s.length() - mark.length() ).trim(); 352 } 353 354 return s; 355 } 356 357 /** 358 * deletes all double entries from the submitted array 359 * 360 * @param s 361 * to remove the doubles from 362 * @return The string array without all doubled values 363 */ 364 public static String[] deleteDoubles( String[] s ) { 365 ArrayList<String> vec = new ArrayList<String>( s.length ); 366 367 for ( int i = 0; i < s.length; i++ ) { 368 if ( !vec.contains( s[i] ) ) { 369 vec.add( s[i] ); 370 } 371 } 372 373 return vec.toArray( new String[vec.size()] ); 374 } 375 376 /** 377 * removes all fields from the array that equals <code>s</code> 378 * 379 * @param target 380 * array where to remove the submitted string 381 * @param s 382 * string to remove 383 * @return the String array with all exact occurrences of given String removed. 384 */ 385 public static String[] removeFromArray( String[] target, String s ) { 386 ArrayList<String> vec = new ArrayList<String>( target.length ); 387 388 for ( int i = 0; i < target.length; i++ ) { 389 if ( !target[i].equals( s ) ) { 390 vec.add( target[i] ); 391 } 392 } 393 394 return vec.toArray( new String[vec.size()] ); 395 } 396 397 /** 398 * checks if the submitted array contains the string <code>value</code> 399 * 400 * @param target 401 * array to check if it contains <code>value</code> 402 * @param value 403 * string to check if it within the array 404 * @return true if the given value is contained (without case comparison) in the array, caution, if the value ends 405 * with a comma ',' a substring will be taken to remove it (rb: For whatever reason??). 406 */ 407 public static boolean contains( String[] target, String value ) { 408 if ( target == null || value == null ) { 409 return false; 410 } 411 412 if ( value.endsWith( "," ) ) { 413 value = value.substring( 0, value.length() - 1 ); 414 } 415 416 for ( int i = 0; i < target.length; i++ ) { 417 if ( value.equalsIgnoreCase( target[i] ) ) { 418 return true; 419 } 420 } 421 422 return false; 423 } 424 425 /** 426 * convert the array of string like [(x1,y1),(x2,y2)...] into an array of double [x1,y1,x2,y2...] 427 * 428 * @param s 429 * @param delimiter 430 * 431 * @return the array representation of the given String 432 */ 433 public static double[] toArrayDouble( String s, String delimiter ) { 434 if ( s == null || "".equals( s.trim() ) ) { 435 return null; 436 } 437 StringTokenizer st = new StringTokenizer( s, delimiter ); 438 439 ArrayList<String> vec = new ArrayList<String>( st.countTokens() ); 440 441 for ( int i = 0; st.hasMoreTokens(); i++ ) { 442 String t = st.nextToken().replace( ' ', '+' ); 443 444 if ( ( t != null ) && ( t.length() > 0 ) ) { 445 vec.add( t.trim().replace( ',', '.' ) ); 446 } 447 } 448 449 double[] array = new double[vec.size()]; 450 451 for ( int i = 0; i < vec.size(); i++ ) { 452 array[i] = Double.parseDouble( vec.get( i ) ); 453 } 454 455 return array; 456 } 457 458 /** 459 * convert the array of string like [(x1,y1),(x2,y2)...] into an array of float values [x1,y1,x2,y2...] 460 * 461 * @param s 462 * @param delimiter 463 * 464 * @return the array representation of the given String 465 */ 466 public static float[] toArrayFloat( String s, String delimiter ) { 467 if ( s == null ) { 468 return null; 469 } 470 471 if ( s.equals( "" ) ) { 472 return null; 473 } 474 475 StringTokenizer st = new StringTokenizer( s, delimiter ); 476 477 ArrayList<String> vec = new ArrayList<String>( st.countTokens() ); 478 for ( int i = 0; st.hasMoreTokens(); i++ ) { 479 String t = st.nextToken().replace( ' ', '+' ); 480 if ( ( t != null ) && ( t.length() > 0 ) ) { 481 vec.add( t.trim().replace( ',', '.' ) ); 482 } 483 } 484 485 float[] array = new float[vec.size()]; 486 487 for ( int i = 0; i < vec.size(); i++ ) { 488 array[i] = Float.parseFloat( vec.get( i ) ); 489 } 490 491 return array; 492 } 493 494 /** 495 * prints current stactrace 496 */ 497 public static void printStacktrace() { 498 System.out.println( StringTools.stackTraceToString( Thread.getAllStackTraces().get( Thread.currentThread() ) ) ); 499 } 500 501 /** 502 * transforms an array of StackTraceElements into a String 503 * 504 * @param se 505 * to put to String 506 * @return a String representation of the given Stacktrace. 507 */ 508 public static String stackTraceToString( StackTraceElement[] se ) { 509 510 StringBuffer sb = new StringBuffer(); 511 for ( int i = 0; i < se.length; i++ ) { 512 sb.append( se[i].getClassName() + " " ); 513 sb.append( se[i].getFileName() + " " ); 514 sb.append( se[i].getMethodName() + "(" ); 515 sb.append( se[i].getLineNumber() + ")\n" ); 516 } 517 return sb.toString(); 518 } 519 520 /** 521 * Get the message and the class, as well as the stack trace of the passed Throwable and transforms it into a String 522 * 523 * @param e 524 * to get information from 525 * @return the String representation of the given Throwable 526 */ 527 public static String stackTraceToString( Throwable e ) { 528 if ( e == null ) { 529 return "No Throwable given."; 530 } 531 StringBuffer sb = new StringBuffer(); 532 sb.append( e.getMessage() ).append( "\n" ); 533 sb.append( e.getClass().getName() ).append( "\n" ); 534 sb.append( stackTraceToString( e.getStackTrace() ) ); 535 return sb.toString(); 536 } 537 538 /** 539 * countString count the occurrences of token into target 540 * 541 * @param target 542 * @param token 543 * 544 * @return the number of occurrences of the given token in the given String 545 */ 546 public static int countString( String target, String token ) { 547 int start = target.indexOf( token ); 548 int count = 0; 549 550 while ( start != -1 ) { 551 count++; 552 start = target.indexOf( token, start + 1 ); 553 } 554 555 return count; 556 } 557 558 /** 559 * Extract all the strings that begin with "start" and end with "end" and store it into an array of String 560 * 561 * @param target 562 * @param startString 563 * @param endString 564 * 565 * @return <code>null</code> if no strings were found!! 566 */ 567 public static String[] extractStrings( String target, String startString, String endString ) { 568 int start = target.indexOf( startString ); 569 570 if ( start == -1 ) { 571 return null; 572 } 573 574 int count = countString( target, startString ); 575 String[] subString = null; 576 if ( startString.equals( endString ) ) { 577 count = count / 2; 578 subString = new String[count]; 579 for ( int i = 0; i < count; i++ ) { 580 int tmp = target.indexOf( endString, start + 1 ); 581 subString[i] = target.substring( start, tmp + 1 ); 582 start = target.indexOf( startString, tmp + 1 ); 583 } 584 } else { 585 subString = new String[count]; 586 for ( int i = 0; i < count; i++ ) { 587 subString[i] = target.substring( start, target.indexOf( endString, start + 1 ) + 1 ); 588 subString[i] = extractString( subString[i], startString, endString, true, true ); 589 start = target.indexOf( startString, start + 1 ); 590 } 591 } 592 593 return subString; 594 } 595 596 /** 597 * extract a string contained between startDel and endDel, you can remove the delimiters if set true the parameters 598 * delStart and delEnd 599 * 600 * @param target 601 * to extract from 602 * @param startDel 603 * to remove from the start 604 * @param endDel 605 * string to remove from the end 606 * @param delStart 607 * true if the start should be removed 608 * @param delEnd 609 * true if the end should be removed 610 * 611 * @return the extracted string from the given target. rb: Caution this method may not do what it should. 612 */ 613 public static String extractString( String target, String startDel, String endDel, boolean delStart, boolean delEnd ) { 614 if ( target == null ) { 615 return null; 616 } 617 int start = target.indexOf( startDel ); 618 619 if ( start == -1 ) { 620 return null; 621 } 622 623 String s = target.substring( start, target.indexOf( endDel, start + 1 ) + 1 ); 624 625 s = s.trim(); 626 627 if ( delStart ) { 628 while ( s.startsWith( startDel ) ) { 629 s = s.substring( startDel.length(), s.length() ).trim(); 630 } 631 } 632 633 if ( delEnd ) { 634 while ( s.endsWith( endDel ) ) { 635 s = s.substring( 0, s.length() - endDel.length() ).trim(); 636 } 637 } 638 639 return s; 640 } 641 642 /** 643 * Initialize the substitution map with all normalization rules for a given locale and add this map to the static 644 * localeMap. 645 * 646 * @param locale 647 * @throws IOException 648 * @throws SAXException 649 * @throws XMLParsingException 650 */ 651 private static void initMap( String locale ) 652 throws IOException, SAXException, XMLParsingException { 653 654 // read normalization file 655 StringBuffer sb = new StringBuffer( 1000 ); 656 InputStream is = StringTools.class.getResourceAsStream( "/normalization.xml" ); 657 if ( is == null ) { 658 is = StringTools.class.getResourceAsStream( "normalization.xml" ); 659 } 660 BufferedReader br = new BufferedReader( new InputStreamReader( is ) ); 661 String s = null; 662 while ( ( s = br.readLine() ) != null ) { 663 sb.append( s ); 664 } 665 br.close(); 666 667 // transform into xml fragment 668 XMLFragment xml = new XMLFragment(); 669 xml.load( new StringReader( sb.toString() ), StringTools.class.getResource( "normalization.xml" ).toString() ); // FIXME 670 671 // create map 672 Map<String, String> substitutionMap = new HashMap<String, String>( 20 ); 673 674 // extract case attrib ( "toLower" or "toUpper" or missing ) for passed locale 675 String xpath = "Locale[@name = '" + Locale.GERMANY.getLanguage() + "']/@case"; 676 String letterCase = XMLTools.getNodeAsString( xml.getRootElement(), xpath, 677 CommonNamespaces.getNamespaceContext(), null ); 678 if ( letterCase != null ) { 679 substitutionMap.put( "case", letterCase ); 680 } 681 682 // extract removeDoubles attrib ( "true" or "false" ) for passed locale 683 xpath = "Locale[@name = '" + Locale.GERMANY.getLanguage() + "']/@removeDoubles"; 684 String removeDoubles = XMLTools.getNodeAsString( xml.getRootElement(), xpath, 685 CommonNamespaces.getNamespaceContext(), null ); 686 if ( removeDoubles != null && removeDoubles.length() > 0 ) { 687 substitutionMap.put( "removeDoubles", removeDoubles ); 688 } 689 690 // extract rules section for passed locale 691 xpath = "Locale[@name = '" + locale + "']/Rule"; 692 List<Node> list = XMLTools.getNodes( xml.getRootElement(), xpath, CommonNamespaces.getNamespaceContext() ); 693 if ( list != null ) { 694 // for ( int i = 0; i < list.size(); i++ ) { 695 for ( Node n : list ) { 696 String src = XMLTools.getRequiredNodeAsString( n, "Source", CommonNamespaces.getNamespaceContext() ); 697 String target = XMLTools.getRequiredNodeAsString( n, "Target", CommonNamespaces.getNamespaceContext() ); 698 substitutionMap.put( src, target ); 699 } 700 } 701 702 // init localeMap if needed 703 if ( localeMap == null ) { 704 localeMap = new HashMap<String, Map<String, String>>( 20 ); 705 } 706 707 localeMap.put( locale, substitutionMap ); 708 } 709 710 /** 711 * The passed string gets normalized along the rules for the given locale as they are set in the file 712 * "./normalization.xml". If such rules are specified, the following order is obeyed: 713 * 714 * <ol> 715 * <li>if the attribute "case" is set with "toLower" or "toUpper", the letters are switched to lower case or to 716 * upper case respectively.</li> 717 * <li>all rules given in the "Rule" elements are performed.</li> 718 * <li>if the attribute "removeDoubles" is set and not empty, all multi occurences of the letters given in this 719 * attribute are reduced to a single occurence.</li> 720 * </ol> 721 * 722 * @param source 723 * the String to normalize 724 * @param locale 725 * the locale language defining the rules to choose, e.g. "de" 726 * @return the normalized String 727 * @throws IOException 728 * @throws SAXException 729 * @throws XMLParsingException 730 */ 731 public static String normalizeString( String source, String locale ) 732 throws IOException, SAXException, XMLParsingException { 733 734 if ( localeMap == null ) { 735 localeMap = new HashMap<String, Map<String, String>>( 20 ); 736 } 737 Map<String, String> substitutionMap = localeMap.get( locale ); 738 739 if ( substitutionMap == null ) { 740 initMap( locale ); 741 } 742 substitutionMap = localeMap.get( locale ); 743 744 String output = source; 745 Set<String> keys = substitutionMap.keySet(); 746 747 boolean toUpper = false; 748 boolean toLower = false; 749 boolean removeDoubles = false; 750 751 for ( String key : keys ) { 752 if ( "case".equals( key ) ) { 753 toUpper = "toUpper".equals( substitutionMap.get( key ) ); 754 toLower = "toLower".equals( substitutionMap.get( key ) ); 755 } 756 if ( "removeDoubles".equals( key ) && substitutionMap.get( key ).length() > 0 ) { 757 removeDoubles = true; 758 } 759 } 760 761 // first: change letters to upper / lower case 762 if ( toUpper ) { 763 output = output.toUpperCase(); 764 } else if ( toLower ) { 765 output = output.toLowerCase(); 766 } 767 768 // second: change string according to specified rules 769 for ( String key : keys ) { 770 if ( !"case".equals( key ) && !"removeDoubles".equals( key ) ) { 771 output = output.replaceAll( key, substitutionMap.get( key ) ); 772 } 773 } 774 775 // third: remove doubles 776 if ( removeDoubles ) { 777 String doubles = substitutionMap.get( "removeDoubles" ); 778 for ( int i = 0; i < doubles.length(); i++ ) { 779 String remove = "" + doubles.charAt( i ) + "+"; 780 String replaceWith = "" + doubles.charAt( i ); 781 output = output.replaceAll( remove, replaceWith ); 782 } 783 } 784 return output; 785 } 786 787 /** 788 * prints a map with one line for each key-value pair 789 * @param map 790 * @param ps if ps is null System.out will be used 791 */ 792 public static final void printMap( Map<?, ?> map, PrintStream ps ) { 793 if ( ps == null ) { 794 ps = System.out; 795 } 796 Iterator<?> iter = map.keySet().iterator(); 797 while ( iter.hasNext() ) { 798 Object key = (Object) iter.next(); 799 Object value = map.get( key ); 800 ps.println( key + " : " + value ); 801 } 802 } 803 }