001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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: 30474 $, $Date: 2011-04-17 11:23:29 +0200 (Sun, 17 Apr 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 * convert the array of string like [(x1,y1),(x2,y2)...] into an array of float values [x1,y1,x2,y2...]
496 *
497 * @param s
498 * @param delimiter
499 *
500 * @return the array representation of the given String
501 */
502 public static int[] toArrayInt( String s, String delimiter ) {
503 if ( s == null ) {
504 return null;
505 }
506
507 if ( s.equals( "" ) ) {
508 return null;
509 }
510
511 StringTokenizer st = new StringTokenizer( s, delimiter );
512
513 ArrayList<String> vec = new ArrayList<String>( st.countTokens() );
514 for ( int i = 0; st.hasMoreTokens(); i++ ) {
515 String t = st.nextToken();
516 if ( ( t != null ) && ( t.length() > 0 ) ) {
517 vec.add( t.trim() );
518 }
519 }
520
521 int[] array = new int[vec.size()];
522
523 for ( int i = 0; i < vec.size(); i++ ) {
524 array[i] = Integer.parseInt( vec.get( i ) );
525 }
526
527 return array;
528 }
529
530 /**
531 * prints current stactrace
532 */
533 public static void printStacktrace() {
534 System.out.println( StringTools.stackTraceToString( Thread.getAllStackTraces().get( Thread.currentThread() ) ) );
535 }
536
537 /**
538 * transforms an array of StackTraceElements into a String
539 *
540 * @param se
541 * to put to String
542 * @return a String representation of the given Stacktrace.
543 */
544 public static String stackTraceToString( StackTraceElement[] se ) {
545
546 StringBuffer sb = new StringBuffer();
547 for ( int i = 0; i < se.length; i++ ) {
548 sb.append( se[i].getClassName() + " " );
549 sb.append( se[i].getFileName() + " " );
550 sb.append( se[i].getMethodName() + "(" );
551 sb.append( se[i].getLineNumber() + ")\n" );
552 }
553 return sb.toString();
554 }
555
556 /**
557 * Get the message and the class, as well as the stack trace of the passed Throwable and transforms it into a String
558 *
559 * @param e
560 * to get information from
561 * @return the String representation of the given Throwable
562 */
563 public static String stackTraceToString( Throwable e ) {
564 if ( e == null ) {
565 return "No Throwable given.";
566 }
567 StringBuffer sb = new StringBuffer();
568 sb.append( e.getMessage() ).append( "\n" );
569 sb.append( e.getClass().getName() ).append( "\n" );
570 sb.append( stackTraceToString( e.getStackTrace() ) );
571 return sb.toString();
572 }
573
574 /**
575 * countString count the occurrences of token into target
576 *
577 * @param target
578 * @param token
579 *
580 * @return the number of occurrences of the given token in the given String
581 */
582 public static int countString( String target, String token ) {
583 int start = target.indexOf( token );
584 int count = 0;
585
586 while ( start != -1 ) {
587 count++;
588 start = target.indexOf( token, start + 1 );
589 }
590
591 return count;
592 }
593
594 /**
595 * Extract all the strings that begin with "start" and end with "end" and store it into an array of String
596 *
597 * @param target
598 * @param startString
599 * @param endString
600 *
601 * @return <code>null</code> if no strings were found!!
602 */
603 public static String[] extractStrings( String target, String startString, String endString ) {
604 int start = target.indexOf( startString );
605
606 if ( start == -1 ) {
607 return null;
608 }
609
610 int count = countString( target, startString );
611 String[] subString = null;
612 if ( startString.equals( endString ) ) {
613 count = count / 2;
614 subString = new String[count];
615 for ( int i = 0; i < count; i++ ) {
616 int tmp = target.indexOf( endString, start + 1 );
617 subString[i] = target.substring( start, tmp + 1 );
618 start = target.indexOf( startString, tmp + 1 );
619 }
620 } else {
621 subString = new String[count];
622 for ( int i = 0; i < count; i++ ) {
623 subString[i] = target.substring( start, target.indexOf( endString, start + 1 ) + 1 );
624 subString[i] = extractString( subString[i], startString, endString, true, true );
625 start = target.indexOf( startString, start + 1 );
626 }
627 }
628
629 return subString;
630 }
631
632 /**
633 * extract a string contained between startDel and endDel, you can remove the delimiters if set true the parameters
634 * delStart and delEnd
635 *
636 * @param target
637 * to extract from
638 * @param startDel
639 * to remove from the start
640 * @param endDel
641 * string to remove from the end
642 * @param delStart
643 * true if the start should be removed
644 * @param delEnd
645 * true if the end should be removed
646 *
647 * @return the extracted string from the given target. rb: Caution this method may not do what it should.
648 */
649 public static String extractString( String target, String startDel, String endDel, boolean delStart, boolean delEnd ) {
650 if ( target == null ) {
651 return null;
652 }
653 int start = target.indexOf( startDel );
654
655 if ( start == -1 ) {
656 return null;
657 }
658
659 String s = target.substring( start, target.indexOf( endDel, start + 1 ) + 1 );
660
661 s = s.trim();
662
663 if ( delStart ) {
664 while ( s.startsWith( startDel ) ) {
665 s = s.substring( startDel.length(), s.length() ).trim();
666 }
667 }
668
669 if ( delEnd ) {
670 while ( s.endsWith( endDel ) ) {
671 s = s.substring( 0, s.length() - endDel.length() ).trim();
672 }
673 }
674
675 return s;
676 }
677
678 /**
679 * Initialize the substitution map with all normalization rules for a given locale and add this map to the static
680 * localeMap.
681 *
682 * @param locale
683 * @throws IOException
684 * @throws SAXException
685 * @throws XMLParsingException
686 */
687 private static void initMap( String locale )
688 throws IOException, SAXException, XMLParsingException {
689
690 // read normalization file
691 StringBuffer sb = new StringBuffer( 1000 );
692 InputStream is = StringTools.class.getResourceAsStream( "/normalization.xml" );
693 if ( is == null ) {
694 is = StringTools.class.getResourceAsStream( "normalization.xml" );
695 }
696 BufferedReader br = new BufferedReader( new InputStreamReader( is ) );
697 String s = null;
698 while ( ( s = br.readLine() ) != null ) {
699 sb.append( s );
700 }
701 br.close();
702
703 // transform into xml fragment
704 XMLFragment xml = new XMLFragment();
705 xml.load( new StringReader( sb.toString() ), StringTools.class.getResource( "normalization.xml" ).toString() ); // FIXME
706
707 // create map
708 Map<String, String> substitutionMap = new HashMap<String, String>( 20 );
709
710 // extract case attrib ( "toLower" or "toUpper" or missing ) for passed locale
711 String xpath = "Locale[@name = '" + Locale.GERMANY.getLanguage() + "']/@case";
712 String letterCase = XMLTools.getNodeAsString( xml.getRootElement(), xpath,
713 CommonNamespaces.getNamespaceContext(), null );
714 if ( letterCase != null ) {
715 substitutionMap.put( "case", letterCase );
716 }
717
718 // extract removeDoubles attrib ( "true" or "false" ) for passed locale
719 xpath = "Locale[@name = '" + Locale.GERMANY.getLanguage() + "']/@removeDoubles";
720 String removeDoubles = XMLTools.getNodeAsString( xml.getRootElement(), xpath,
721 CommonNamespaces.getNamespaceContext(), null );
722 if ( removeDoubles != null && removeDoubles.length() > 0 ) {
723 substitutionMap.put( "removeDoubles", removeDoubles );
724 }
725
726 // extract rules section for passed locale
727 xpath = "Locale[@name = '" + locale + "']/Rule";
728 List<Node> list = XMLTools.getNodes( xml.getRootElement(), xpath, CommonNamespaces.getNamespaceContext() );
729 if ( list != null ) {
730 // for ( int i = 0; i < list.size(); i++ ) {
731 for ( Node n : list ) {
732 String src = XMLTools.getRequiredNodeAsString( n, "Source", CommonNamespaces.getNamespaceContext() );
733 String target = XMLTools.getRequiredNodeAsString( n, "Target", CommonNamespaces.getNamespaceContext() );
734 substitutionMap.put( src, target );
735 }
736 }
737
738 // init localeMap if needed
739 if ( localeMap == null ) {
740 localeMap = new HashMap<String, Map<String, String>>( 20 );
741 }
742
743 localeMap.put( locale, substitutionMap );
744 }
745
746 /**
747 * The passed string gets normalized along the rules for the given locale as they are set in the file
748 * "./normalization.xml". If such rules are specified, the following order is obeyed:
749 *
750 * <ol>
751 * <li>if the attribute "case" is set with "toLower" or "toUpper", the letters are switched to lower case or to
752 * upper case respectively.</li>
753 * <li>all rules given in the "Rule" elements are performed.</li>
754 * <li>if the attribute "removeDoubles" is set and not empty, all multi occurences of the letters given in this
755 * attribute are reduced to a single occurence.</li>
756 * </ol>
757 *
758 * @param source
759 * the String to normalize
760 * @param locale
761 * the locale language defining the rules to choose, e.g. "de"
762 * @return the normalized String
763 * @throws IOException
764 * @throws SAXException
765 * @throws XMLParsingException
766 */
767 public static String normalizeString( String source, String locale )
768 throws IOException, SAXException, XMLParsingException {
769
770 if ( localeMap == null ) {
771 localeMap = new HashMap<String, Map<String, String>>( 20 );
772 }
773 Map<String, String> substitutionMap = localeMap.get( locale );
774
775 if ( substitutionMap == null ) {
776 initMap( locale );
777 }
778 substitutionMap = localeMap.get( locale );
779
780 String output = source;
781 Set<String> keys = substitutionMap.keySet();
782
783 boolean toUpper = false;
784 boolean toLower = false;
785 boolean removeDoubles = false;
786
787 for ( String key : keys ) {
788 if ( "case".equals( key ) ) {
789 toUpper = "toUpper".equals( substitutionMap.get( key ) );
790 toLower = "toLower".equals( substitutionMap.get( key ) );
791 }
792 if ( "removeDoubles".equals( key ) && substitutionMap.get( key ).length() > 0 ) {
793 removeDoubles = true;
794 }
795 }
796
797 // first: change letters to upper / lower case
798 if ( toUpper ) {
799 output = output.toUpperCase();
800 } else if ( toLower ) {
801 output = output.toLowerCase();
802 }
803
804 // second: change string according to specified rules
805 for ( String key : keys ) {
806 if ( !"case".equals( key ) && !"removeDoubles".equals( key ) ) {
807 output = output.replaceAll( key, substitutionMap.get( key ) );
808 }
809 }
810
811 // third: remove doubles
812 if ( removeDoubles ) {
813 String doubles = substitutionMap.get( "removeDoubles" );
814 for ( int i = 0; i < doubles.length(); i++ ) {
815 String remove = "" + doubles.charAt( i ) + "+";
816 String replaceWith = "" + doubles.charAt( i );
817 output = output.replaceAll( remove, replaceWith );
818 }
819 }
820 return output;
821 }
822
823 /**
824 * prints a map with one line for each key-value pair
825 * @param map
826 * @param ps if ps is null System.out will be used
827 */
828 public static final void printMap( Map<?, ?> map, PrintStream ps ) {
829 if ( ps == null ) {
830 ps = System.out;
831 }
832 Iterator<?> iter = map.keySet().iterator();
833 while ( iter.hasNext() ) {
834 Object key = (Object) iter.next();
835 Object value = map.get( key );
836 ps.println( key + " : " + value );
837 }
838 }
839 }