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 }