001    package com.saelist.util;
002    
003    import org.apache.log4j.*;
004    import java.io.*;
005    import java.util.*;
006    
007    
008    /**
009     * Various text oriented utilites
010     *
011     * @author Kristmundsson
012     *
013     * @created 4. September 2002
014     */
015    public class Strings {
016    
017      /** A logger for this class. */
018    
019      public static Logger logger = Logger.getLogger("com.saelist.util.Strings");
020    
021      /**
022       * Returns the stacktrace of the given exception as a string.
023       *
024       * @param e the exception to trace.
025       *
026       * @return the stacktrace of the exception as a string.
027       */
028      public static String asStackTrace(Throwable e) {
029        StringWriter writer = new StringWriter();
030        e.printStackTrace(new PrintWriter(writer));
031    
032        return writer.toString();
033      }
034    
035      /**
036       * Tests if the text contains any of the characters in chars .
037       *
038       * @param text the string to search.
039       * @param chars the chars to find.
040       *
041       * @return weather text conains any of the characters.
042       */
043      public static boolean containsAnyOf(String text, String chars) {
044        for(int i = 0; i < chars.length(); i++)
045          if(text.indexOf((char) chars.charAt(i)) >= 0)
046            return true;
047    
048        return false;
049      }
050    
051      /**
052       * Limits the size of text to maxSize charaters and appends the suffix.
053       *
054       * @param text the text to constrain.
055       * @param maxSize the desired maximum length of text.
056       * @param suffix will be appended to the result if it is actaully cut down.
057       *
058       * @return The cut-down string with the suffix appended if it exceeded
059       *         length. otherwise the original text.
060       *
061       * @throws NullPointerException is text is null.
062       */
063      public static String cutDown(String text, int maxSize, String suffix) {
064        if(text!=null) {
065          if(text.length() > maxSize) {
066            if(suffix!=null)
067              return text.substring(0, maxSize) + suffix;
068          }
069        }
070        return text;
071      }
072    
073      /**
074       * A conveniance interface to {@link #format(String, String[])} for a single
075       * argument.
076       */
077      public static String format(String text, String arg) {
078        return format(text, new String[] { arg });
079      }
080    
081      /**
082       * A conveniance interface to {@link #format(String, String[])} for 2
083       * arguments.
084       */
085      public static String format(String text, String arg1, String arg2) {
086        return format(text, new String[] { arg1, arg2 });
087      }
088    
089      /**
090       * A conveniance interface to {@link #format(String, String[])} for 3
091       * arguments.
092       */
093      public static String format(String text, String arg1, String arg2, String arg3) {
094        return format(text, new String[] { arg1, arg2, arg3 });
095      }
096    
097      /**
098       * A conveniance interface to {@link #format(String, String[])} for 4
099       * arguments.
100       */
101      public static String format(String text, String arg1, String arg2, String arg3, String arg4) {
102        return format(text, new String[] { arg1, arg2, arg3, arg4 });
103      }
104    
105      /**
106       * Replaces each occurence of "%" in the text with the eachth element of
107       * args. The process stops when it runs out of either.
108       *
109       * @param text the string to format.
110       * @param args the replacements for "%" in text.
111       *
112       * @return the formatted string
113       */
114      public static String format(String text, String[] args) {
115        int pos0 = 0;
116        int pos1 = text.indexOf("%");
117        StringBuffer result = new StringBuffer();
118    
119        for(int i = 0; (i < args.length) && (pos1 >= 0); i++) {
120          result.append(text.substring(pos0, pos1)).append(args[i]);
121          pos0 = pos1 + 1;
122          pos1 = text.indexOf("%", pos0);
123        }
124    
125        result.append(text.substring(pos0));
126    
127        // System.out.println(text + " -> " + result.toString());
128        return result.toString();
129      }
130    
131      /**
132       * Indents a string by n characters. Inserts n spaces at the front of the
133       * string and after all occurences of newline. Un-optimized.
134       *
135       * @param text the string to indent
136       * @param n the number of spaces to indent with.
137       *
138       * @return the indented string.
139       */
140      public static String indent(String text, int n) {
141        String spaces = repeat(" ", n);
142    
143        return spaces + replace(text, "\n", "\n" + spaces);
144      }
145    
146      /**
147       * Primitive variable substitution for strings. For any key x in the
148       * substitutions map the corresponding value will replace any subtrings of
149       * the form {x} int the text.
150       *
151       * @param text the string to be changed
152       * @param substitutions maps patterns to replacements.
153       *
154       * @return the changed string.
155       */
156      public static String interpret(String text, Map substitutions) {
157        int left = 0;
158        int right = 0;
159    
160        if(text != null)
161          do {
162            left = text.indexOf('{', right);
163    
164            if(left >= 0) {
165              right = text.indexOf('}', left + 1);
166    
167              if(right >= 0)
168                text = text.substring(0, left) +
169                       substitutions.get(text.substring(left + 1, right)) +
170                       text.substring(Math.min(text.length(), right + 1));
171            }
172          } while((left > 0) && (right > 0));
173    
174        return text;
175      }
176    
177      /**
178       * Joins a List [a, b, c] into a string "a|b|c". Nulls are shown as empty
179       * strings ("a||c").
180       *
181       * @param list the list to join. Must not be null.
182       * @param separator will be inserted between the joined elements. If
183       *        separator is null the string "null" will be inserted. To insert
184       *        nothing provide the empty string.
185       *
186       * @return the joined string.
187       *
188       * @throws NullPointerException if list is null.
189       */
190      public static String join(List list, String separator) {
191        StringBuffer result = new StringBuffer();
192    
193        for(int i = 0; i < (list.size() - 1); i++)
194          result.append(toNonNullString(list.get(i))).append(separator);
195    
196        if(list.size() > 0)
197          result.append(toNonNullString(list.get(list.size() - 1)));
198    
199        return result.toString();
200      }
201    
202      /**
203       * See {@link #join(List, String)}.
204       */
205      public static String join(Object[] objects, String delimiter) {
206        return join(Arrays.asList(objects), delimiter);
207      }
208    
209      /**
210       * Load the contents of a file as a string.
211       *
212       * @param filename the name of the file to load.
213       *
214       * @return the contents of the file as a string.
215       *
216       * @throws IOException if the file indicated cannot be accessed.
217       */
218      public static String loadFile(String filename) throws IOException {
219        return loadStream(new FileInputStream(new File(filename)));
220      }
221    
222      public static void saveFile(String filename, String content) throws IOException {
223        logger.info("saveFile(): filename=" + filename);
224        OutputStream out = new FileOutputStream(filename);
225        out.write(content.getBytes());
226        out.flush();
227        out.close();
228      }
229    
230      /**
231       * Loads the content of reader into a String.
232       *
233       * @param reader The reader to empty;
234       *
235       * @return the read string;
236       *
237       * @throws IOException if the file indicated cannot be accessed.
238       */
239      public static String loadReader(Reader reader) throws IOException {
240        BufferedReader bufferedReader;
241    
242        if(!(reader instanceof BufferedReader))
243          bufferedReader = new BufferedReader(reader);
244        else
245          bufferedReader = (BufferedReader) reader;
246    
247        StringBuffer buffer = new StringBuffer();
248        int i;
249    
250        while((i = bufferedReader.read()) >= 0)
251          buffer.append((char) i);
252    
253        return buffer.toString();
254      }
255    
256      /**
257       * Load a resource (file) on the classpath as a string.
258       *
259       * @param name the fully qualified name of the file to load. E.g.
260       *        /com/acme/stardust.xml, the name only if the file is resides in
261       *        the same package as clazz.
262       * @param clazz the class in whose package the resource will be sought.
263       *
264       * @return the loaded resource as a string.
265       *
266       * @throws Exception if the resource is not found.
267       */
268      public static String loadResource(String name, Class clazz)
269      throws Exception {
270        java.io.InputStream in = clazz.getResourceAsStream(name);
271        verify(in != null, "Error loading resource " + name);
272    
273        return loadStream(in);
274      }
275    
276      /**
277       * Load a resource (file) on the classpath as a string.
278       *
279       * @param name the fully qualified name of the file to load. E.g.
280       *        /com/acme/stardust.xml (or the resource must reside in the same
281       *        package as this class.
282       *
283       * @return the loaded resource as a string.
284       *
285       * @throws Exception If the resource is not found or cannot be accessed.
286       */
287      public static String loadResource(String name) throws Exception {
288        return loadResource(name, String.class);
289      }
290    
291      /**
292       * Load all content from the given stream as string.
293       *
294       * @param in the stream to load from.
295       *
296       * @return the content of the stream.
297       *
298       * @throws IOException thrown if the Stream throws it.
299       */
300      public static String loadStream(InputStream in) throws IOException {
301        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
302        StringBuffer buffer = new StringBuffer();
303        int i;
304    
305        while((i = reader.read()) >= 0)
306          buffer.append((char) i);
307    
308        return buffer.toString();
309      }
310    
311      /**
312       * Repeat the given string n times. precondition: n >= 0; Un-optimized.
313       *
314       * @param s the string to repeat
315       * @param n the number of times to repeat
316       *
317       * @return the repeated string.
318       */
319      public static String repeat(String s, int n) {
320        if(n <= 0)
321          return "";
322    
323        return s + repeat(s, n - 1);
324      }
325    
326      /**
327       * Substitutes all occurances of a pattern in a text replacementwith
328       * replacement.
329       *
330       * @param text the string to be changed
331       * @param pattern the substring of text to be replaced
332       * @param replacement the string to replace pattern in text.
333       *
334       * @return the affected string
335       */
336      public static String replace(String text, String pattern, String replacement) {
337        return replace(text, pattern, replacement, Integer.MAX_VALUE);
338      }
339    
340      /**
341       * Substitutes the first count occurances of a pattern in a text with
342       * replacement. Note that the empty string matches anything so the
343       * replacement will be repeated count times
344       *
345       * @param text the string to be changed
346       * @param pattern the substring of text to be replaced must be a nonempty
347       *        string.
348       * @param replacement the string to replace pattern in text. Must not be
349       *        null.
350       * @param count how many of the first instances of pattern to replace.
351       *
352       * @return the resulting string
353       */
354      public static String replace(String text, String pattern, String replacement,
355                                   int count) {
356        // System.out.println(text + "\t" + pattern + "\t"  + replacement);
357        int pos = text.indexOf(pattern);
358    
359        if((pos < 0) || (count == 0))
360          return text;
361    
362        return text.substring(0, pos) + replacement +
363               replace(text.substring(pos + pattern.length()), pattern, replacement,
364                       count - 1);
365      }
366    
367      /**
368       * Splits a string <b>"a|b|c"</b> into list with elements <b>["a", "b", "c"]
369       * </b>. Empty strings on either side of the separator are correctly
370       * identified and added to the list. An empty string without a separator
371       * returns an empty list as does null
372       *
373       * @param text the entities to put in the list separated by the separator.
374       * @param separator separates the entities to put in the list.
375       *
376       * @return A list. (never null).
377       */
378      public static List split(String text, String separator) {
379        List result = new ArrayList();
380    
381        if((text == null) || text.equals(""))
382          return result;
383    
384        StringTokenizer t = new StringTokenizer(text, separator, true);
385        String lastToken = separator;
386        String token;
387    
388        while(t.hasMoreTokens()) {
389          token = t.nextToken();
390    
391          if(separator.equals(token)) {
392            if(separator.equals(lastToken))
393              result.add("");
394          } else
395            result.add(token);
396    
397          lastToken = token;
398        }
399    
400        if(separator.equals(lastToken))
401          result.add("");
402    
403        return result;
404      }
405    
406      /**
407       * Tolerant substring. Text can be null and stop can be beyound the end and
408       * start can be beyond stop. Start can be negative.
409       *
410       * @param text the string to take the substring from. Can be null .
411       * @param start the position of the first character of the substring in text.
412       *        Can be less than stop, in which case it is set to the value of
413       *        stop .
414       * @param stop the position after the last character of the substring in
415       *        text. Can be beyond the length of text, in which case it is set to
416       *        the length text.
417       *
418       * @return the substring if any, otherwise "".
419       */
420      public static String substring(String text, int start, int stop) {
421        if(text == null)
422          return null;
423    
424        if(start < 0)
425          start = 0;
426    
427        if(stop > text.length())
428          stop = text.length();
429    
430        if(start > stop)
431          start = stop;
432    
433        return text.substring(start, stop);
434      }
435    
436      /**
437       * Tolerant substring. Text can be null. Start can be negative.
438       *
439       * @param text the string to take the substring from. Can be null .
440       * @param start the position of the first character of the substring in text.
441       *        Can be negative, in which case it is set to null. Can be beyond
442       *        the end of the string in which case it is set to the length of the
443       *        string.
444       *
445       * @return the part of text from start to the end of the string. Null if text
446       *         is null.
447       */
448      public static String substring(String text, int start) {
449        if(text == null)
450          return null;
451    
452        return substring(text, start, text.length());
453      }
454    
455      /**
456       * En{@link java.util.List list}s an {@link java.util.Enumeration
457       * enumeration}.
458       *
459       * @param e the enumeration to enlist.
460       *
461       * @return the enlisted enumeration.
462       */
463      public static List toList(Enumeration e) {
464        List list = new ArrayList();
465    
466        while(e.hasMoreElements())
467          list.add(e.nextElement());
468    
469        return list;
470      }
471    
472      /**
473       * Converts String array to a string with delimiter. Ignores elements that
474       * are null, "" or "-1".
475       *
476       * @param values to be concatenated.
477       * @param separator inserted between values in the returned string.
478       *
479       * @return the concatenated values if any or null otherwise.
480       */
481      public static String arrayToString(String[] values, char separator) {
482        StringBuffer buffer = new StringBuffer();
483    
484        for(int i = 0; i < values.length; i++)
485          if((values[i] != "") && (values[i] != null) &&
486                  !values[i].equalsIgnoreCase("-1"))
487            buffer.append(values[i] + separator);
488    
489        if(buffer.length() == 0)
490          return null;
491        else
492    
493          //Take off the last delimiter and turn it into a String
494          return buffer.substring(0, buffer.length() - 1);
495      }
496    
497      /**
498       * Returns the empty string if value is null or "null".
499       *
500       * @param value to be converted.
501       *
502       * @return the possibly converted string.
503       */
504      public static String removeNull(String value) {
505        if((value == null) || value.equalsIgnoreCase("null"))
506          return "";
507    
508        return value;
509      }
510    
511      /**
512       * Converts null strings to "", all others objects to their {@link
513       * java.lang.Object#toString()}.
514       *
515       * @param object the object to stringify.
516       *
517       * @return Returns an empty string if the given string is null otherwise
518       *         returns the givent string.
519       */
520      public static String toNonNullString(Object object) {
521        if(object == null)
522          return "";
523    
524        return object.toString();
525      }
526    
527      /**
528       * Converts a {@link java.util.List} of Objects into a array of strings.
529       *
530       * @param list the list to turn into an array. Null values become "null".
531       *        Other values become their toString() results.
532       *
533       * @return the array of strings.
534       */
535      public static String[] toStringArray(List list) {
536        String[] result = new String[list.size()];
537    
538        for(int i = 0; i < list.size(); i++)
539          result[i] = "" + list.get(i);
540    
541        return result;
542      }
543    
544      /**
545       * Converts a {@link java.util.List} into a array of strings.
546       *
547       * @param list the list to turn into an array.  Null values become "null".
548       *        Other values become their toString() results.
549       *
550       * @return the array of strings.
551       */
552      /* public static String[] toStringArray(List list, String elementname) {
553        //Fill products and accessories
554        String[] result = new String[list.size()];
555        Element element;
556    
557        for(int i = 0; i < list.size(); i++) {
558          element = (Element) list.get(i);
559          result[i] = element.getChildText(elementname);
560        }
561    
562        return result;
563      } */
564    
565      /**
566       * Splits the text on the delimiters and returns an array of the strings. If
567       * returndelimiters is true, then the delimiters are are also returned as
568       * tokens.
569       *
570       * <p>
571       * Note: Since it uses {@link java.util.StringTokenizer}, empty strings are
572       * simply skipped and do not appear in the list. Use {@link #split split}
573       * for more consistent behaviour.
574       * </p>
575       *
576       * @param text the text to split
577       * @param delimiters on which to split
578       * @param returndelimiters true to get the delimiters also.
579       *
580       * @return an array of the split strings.
581       */
582      public static String[] tokenize(String text, String delimiters,
583                                      boolean returndelimiters) {
584        List list = new Vector();
585        StringTokenizer tokenizer = new StringTokenizer(text, delimiters,
586                                    returndelimiters);
587    
588        while(tokenizer.hasMoreTokens())
589          list.add(tokenizer.nextToken());
590    
591        return (String[]) list.toArray(new String[list.size()]);
592      }
593    
594      /**
595       * Throws an exception with the given message if the condition is true.
596       *
597       * @param condition the condition to test.
598       * @param message The message to give the exception.
599       *
600       * @throws Exception if condition is true.
601       */
602      public static void verify(boolean condition, String message)
603      throws Exception {
604        if(!condition)
605          throw new Exception(message);
606      }
607    
608      /**
609       * Indicates wether the pattern matches the text. The only wildcard allowed
610       * is the asterisk.
611       *
612       * @param text the text to check for a match.
613       * @param pattern the pattern to match to text. An asterisk stands for zero
614       *        or more characters.
615       *
616       * @return true if the pattern matches the text .
617       */
618      public static boolean matches(String text, String pattern) {
619        StringTokenizer t = new StringTokenizer(pattern, "*", true);
620        List list = new ArrayList();
621    
622        while(t.hasMoreTokens())
623          list.add(t.nextToken());
624    
625        //System.out.println(list);
626        return matches(text, 0, (String[]) list.toArray(new String[0]), 0);
627      }
628    
629      /**
630       * Implements what matches(text, pattern) promises.
631       *
632       * @param text the string to match to the pattern.
633       * @param pos how far into the text we are.
634       * @param tokens the tokenized pattern.
635       * @param tpos the tokenized pattern we are.
636       *
637       * @return true if the token list (from tpos) matched the text (from pos).
638       */
639      private static boolean matches(String text, int pos, String[] tokens, int tpos) {
640        // System.out.println("pos=" + pos + ", tops=" + tpos);
641        if(tpos < tokens.length) {
642          String token = tokens[tpos];
643    
644          if(token.equals("*")) {
645            for(int i = 0; (pos + i) <= text.length(); i++)
646              if(matches(text, pos + i, tokens, tpos + 1))
647                return true;
648    
649            return false;
650          } else {
651            if(pos == text.indexOf(token))
652              return matches(text, pos + token.length(), tokens, tpos + 1);
653    
654            return false;
655          }
656        } else
657    
658          return pos == text.length();
659      }
660    
661      /**
662       * Returns the stack-trace of a {@link java.lang.Throwable} as String.
663       *
664       * @param t the {@link java.lang.Throwable} to trace:
665       *
666       * @return a string showing the stack when the exception occured.
667       */
668      public static String getStackTrace(Throwable t) {
669        StringWriter stringWriter = new StringWriter();
670        PrintWriter printWriter = new PrintWriter(stringWriter);
671        t.printStackTrace(printWriter);
672        printWriter.flush();
673    
674        return stringWriter.toString();
675      }
676    
677      /**
678       * Returns the part of text before sep. If sep is not part of text the whole
679       * text is returned.
680       *
681       * @param text The text to get the front off.
682       * @param sep separating the front from the rest.
683       *
684       * @return the part of text in front of sep.
685       */
686      public static String front(String text, String sep) {
687        int pos = text.indexOf(sep);
688    
689        if(pos >= 0) {
690          return text.substring(0, pos);
691        }
692    
693        return text;
694      }
695    
696      /**
697       * Returns the part of text before the last sep. If sep is not part of text
698       * the whole text is returned.
699       *
700       * @param text The text to get the front off.
701       * @param sep separating the front from the rest.
702       *
703       * @return the part of text in front of sep.
704       */
705      public static String lastFront(String text, String sep) {
706        int pos = text.lastIndexOf(sep);
707    
708        if(pos >= 0) {
709          return text.substring(0, pos);
710        }
711    
712        return text;
713      }
714    
715      /**
716       * Returns the rest of text after first sep. If sep is not part of test "" is
717       * returned.
718       *
719       * @param text The text to get the rest off.
720       * @param sep separating the rest from the front.
721       *
722       * @return the part of text after sep.
723       */
724      public static String rest(String text, String sep) {
725        int pos = text.indexOf(sep);
726    
727        if((pos >= 0) && (pos < text.length())) {
728          return text.substring(pos + 1);
729        }
730    
731        return "";
732      }
733    
734      /**
735       * Returns the rest of text after last sep. If sep is not part of test "" is
736       * returned.
737       *
738       * @param text The text to get the rest off.
739       * @param sep separating the rest from the front.
740       *
741       * @return the part of text after sep.
742       */
743      public static String lastRest(String text, String sep) {
744        int pos = text.lastIndexOf(sep);
745    
746        if((pos >= 0) && (pos < text.length())) {
747          return text.substring(pos + 1);
748        }
749    
750        return "";
751      }
752    
753      /**
754       * A convenience method to make an empty list.
755       */
756      public static List makeList() {
757        return new ArrayList(0);
758      }
759    
760      /**
761       * A convenience method to make a one element list.
762       */
763      public static List makeList(Object arg1) {
764        List list = new ArrayList(1);
765        list.add(arg1);
766    
767        return list;
768      }
769    
770      /**
771       * A convenience method to make a two element list.
772       */
773      public static List makeList(Object arg1, Object arg2) {
774        List list = new ArrayList(2);
775        list.add(arg1);
776        list.add(arg2);
777    
778        return list;
779      }
780    
781      /**
782       * A convenience method to make a three element list.
783       */
784      public static List makeList(Object arg1, Object arg2, Object arg3) {
785        List list = new ArrayList(3);
786        list.add(arg1);
787        list.add(arg2);
788        list.add(arg3);
789    
790        return list;
791      }
792    
793      /**
794       * A convenience method to make a four element list.
795       */
796      public static List makeList(Object arg1, Object arg2, Object arg3, Object arg4) {
797        List list = new ArrayList(4);
798        list.add(arg1);
799        list.add(arg2);
800        list.add(arg3);
801        list.add(arg4);
802    
803        return list;
804      }
805    
806      /**
807       * A convenience method to enlist an array.
808       */
809      public static List makeList(Object[] args) {
810        List list = new ArrayList(args.length);
811    
812        for(int i = 0; i < args.length; i++)
813          list.add(args[i]);
814    
815        return list;
816      }
817    
818      /**
819       * A convenience method to make a one entry map.
820       */
821      public static Map makeMap(Object key1, Object value1) {
822        Map result = new HashMap();
823        result.put(key1, value1);
824    
825        return result;
826      }
827    
828      /**
829       * A convenience method to make a two entry map.
830       */
831      public static Map makeMap(Object key1, Object value1, Object key2,
832                                Object value2) {
833        Map result = new HashMap();
834        result.put(key1, value1);
835        result.put(key2, value2);
836    
837        return result;
838      }
839    
840      /**
841       * A convenience method to make a three entry map.
842       */
843      public static Map makeMap(Object key1, Object value1, Object key2,
844                                Object value2, Object key3, Object value3) {
845        Map result = new HashMap();
846        result.put(key1, value1);
847        result.put(key2, value2);
848        result.put(key3, value3);
849    
850        return result;
851      }
852    
853      /**
854       * A convenience method to make a four entry map.
855       */
856      public static Map makeMap(Object key1, Object value1, Object key2,
857                                Object value2, Object key3, Object value3, Object key4, Object value4) {
858        Map result = new HashMap();
859        result.put(key1, value1);
860        result.put(key2, value2);
861        result.put(key3, value3);
862        result.put(key4, value4);
863    
864        return result;
865      }
866    
867      /**
868       * @return a new list with each element of the given list wrapped between the
869       *         prefix and postfix strings.
870       */
871      public static List wrap(String prefix, List list, String suffix) {
872        List result = new ArrayList(list.size());
873    
874        for(Iterator it = list.iterator(); it.hasNext();)
875          result.add(prefix + it.next() + suffix);
876    
877        return result;
878      }
879    }
880