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