View Javadoc

1   package org.apache.velocity.tools.generic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.lang.reflect.Array;
23  import java.text.MessageFormat;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Iterator;
28  import java.util.regex.Pattern;
29  
30  import org.apache.commons.beanutils.PropertyUtils;
31  import org.apache.velocity.tools.config.DefaultKey;
32  
33  /**
34   * Provides general utility methods for controlling the display of references.
35   * Currently, this class contains methods for "pretty printing" an array or
36   * {@link Collection}, methods for truncating the string value of a reference
37   * at a configured or specified length, methods for displaying an alternate
38   * value when a specified value is null, a method for generating whitespace, 
39   * a "printf" type of method for formatting messages, and
40   * methods for forcing values into "cells" of equal size (via truncation or
41   * padding with whitespace).
42   *
43   * <p><b>Example Use:</b>
44   * <pre>
45   * tools.xml...
46   * &lt;tools&gt;
47   *   &lt;toolbox scope="application"&gt;
48   *     &lt;tool class="org.apache.velocity.tools.generic.DisplayTool"/&gt;
49   *   &lt;/toolbox&gt;
50   * &lt;/tools&gt;
51   *
52   * template...
53   *   #set( $list = [1..5] )
54   *   $display.list($list)
55   *   $display.truncate("This is a long string.", 10)
56   *   Not Null: $display.alt("not null", "--")
57   *   Null: $display.alt($null, "--")
58   *
59   * output...
60   *   1, 2, 3, 4 and 5
61   *   This is...
62   *   Not Null: not null
63   *   Null: --
64   *   
65   * </pre></p>
66   *
67   * @since VelocityTools 2.0
68   * @author <a href="sean@somacity.com">Sean Legassick</a>
69   * @author <a href="dlr@collab.net">Daniel Rall</a>
70   * @author Nathan Bubna
71   * @version $Id: DisplayTool.java 463298 2006-10-12 16:10:32Z henning $
72   */
73  @DefaultKey("display")
74  public class DisplayTool extends LocaleConfig
75  {
76      public static final String LIST_DELIM_KEY = "listDelim";
77      public static final String LIST_FINAL_DELIM_KEY = "listFinalDelim";
78      public static final String TRUNCATE_LENGTH_KEY = "truncateLength";
79      public static final String TRUNCATE_SUFFIX_KEY = "truncateSuffix";
80      public static final String TRUNCATE_AT_WORD_KEY = "truncateAtWord";
81      public static final String CELL_LENGTH_KEY = "cellLength";
82      public static final String CELL_SUFFIX_KEY = "cellSuffix";
83      public static final String DEFAULT_ALTERNATE_KEY = "defaultAlternate";
84      public static final String ALLOWED_TAGS_KEY = "allowedTags";
85  
86      private String defaultDelim = ", ";
87      private String defaultFinalDelim = " and ";
88      private int defaultTruncateLength = 30;
89      private String defaultTruncateSuffix = "...";
90      private boolean defaultTruncateAtWord = false;
91      private int defaultCellLength = 30;
92      private String defaultCellSuffix = "...";
93      private String defaultAlternate = "null";
94      private String[] defaultAllowedTags = null;
95  
96      /**
97       * Does the actual configuration. This is protected, so
98       * subclasses may share the same ValueParser and call configure
99       * at any time, while preventing templates from doing so when 
100      * configure(Map) is locked.
101      */
102     protected void configure(ValueParser values)
103     {
104         String listDelim = values.getString(LIST_DELIM_KEY);
105         if (listDelim != null)
106         {
107             setListDelimiter(listDelim);
108         }
109 
110         String listFinalDelim = values.getString(LIST_FINAL_DELIM_KEY);
111         if (listFinalDelim != null)
112         {
113             setListFinalDelimiter(listFinalDelim);
114         }
115 
116         Integer truncateLength = values.getInteger(TRUNCATE_LENGTH_KEY);
117         if (truncateLength != null)
118         {
119             setTruncateLength(truncateLength);
120         }
121 
122         String truncateSuffix = values.getString(TRUNCATE_SUFFIX_KEY);
123         if (truncateSuffix != null)
124         {
125             setTruncateSuffix(truncateSuffix);
126         }
127 
128         Boolean truncateAtWord = values.getBoolean(TRUNCATE_AT_WORD_KEY);
129         if (truncateAtWord != null)
130         {
131             setTruncateAtWord(truncateAtWord);
132         }
133 
134         Integer cellLength = values.getInteger(CELL_LENGTH_KEY);
135         if (cellLength != null)
136         {
137             setCellLength(cellLength);
138         }
139 
140         String cellSuffix = values.getString(CELL_SUFFIX_KEY);
141         if (cellSuffix != null)
142         {
143             setCellSuffix(cellSuffix);
144         }
145 
146         String defaultAlternate = values.getString(DEFAULT_ALTERNATE_KEY);
147         if (defaultAlternate != null)
148         {
149             setDefaultAlternate(defaultAlternate);
150         }
151 
152         String[] allowedTags = values.getStrings(ALLOWED_TAGS_KEY);
153         if (allowedTags != null)
154         {
155             setAllowedTags(allowedTags);
156         }
157     }
158 
159     public String getListDelimiter()
160     {
161         return this.defaultDelim;
162     }
163 
164     protected void setListDelimiter(String delim)
165     {
166         this.defaultDelim = delim;
167     }
168 
169     public String getListFinalDelimiter()
170     {
171         return this.defaultFinalDelim;
172     }
173 
174     protected void setListFinalDelimiter(String finalDelim)
175     {
176         this.defaultFinalDelim = finalDelim;
177     }
178 
179     public int getTruncateLength()
180     {
181         return this.defaultTruncateLength;
182     }
183 
184     protected void setTruncateLength(int maxlen)
185     {
186         this.defaultTruncateLength = maxlen;
187     }
188 
189     public String getTruncateSuffix()
190     {
191         return this.defaultTruncateSuffix;
192     }
193 
194     protected void setTruncateSuffix(String suffix)
195     {
196         this.defaultTruncateSuffix = suffix;
197     }
198 
199     public boolean getTruncateAtWord()
200     {
201         return this.defaultTruncateAtWord;
202     }
203 
204     protected void setTruncateAtWord(boolean atWord)
205     {
206         this.defaultTruncateAtWord = atWord;
207     }
208 
209     public String getCellSuffix()
210     {
211         return this.defaultCellSuffix;
212     }
213 
214     protected void setCellSuffix(String suffix)
215     {
216         this.defaultCellSuffix = suffix;
217     }
218 
219     public int getCellLength()
220     {
221         return this.defaultCellLength;
222     }
223 
224     protected void setCellLength(int maxlen)
225     {
226         this.defaultCellLength = maxlen;
227     }
228 
229     public String getDefaultAlternate()
230     {
231         return this.defaultAlternate;
232     }
233 
234     protected void setDefaultAlternate(String dflt)
235     {
236         this.defaultAlternate = dflt;
237     }
238 
239     public String[] getAllowedTags()
240     {
241         return this.defaultAllowedTags;
242     }
243 
244     protected void setAllowedTags(String[] tags)
245     {
246         this.defaultAllowedTags = tags;
247     }
248 
249 
250     /**
251      * Formats a collection or array into the form "A, B and C".
252      *
253      * @param list A collection or array.
254      * @return A String.
255      */
256     public String list(Object list)
257     {
258         return list(list, this.defaultDelim, this.defaultFinalDelim);
259     }
260 
261     /**
262      * Formats a collection or array into the form
263      * "A&lt;delim&gt;B&lt;delim&gt;C".
264      *
265      * @param list A collection or array.
266      * @param delim A String.
267      * @return A String.
268      */
269     public String list(Object list, String delim)
270     {
271         return list(list, delim, delim);
272     }
273 
274     /**
275      * Formats a collection or array into the form
276      * "A&lt;delim&gt;B&lt;finaldelim&gt;C".
277      * 
278      * @param list A collection or array.
279      * @param delim A String.
280      * @param finaldelim A String.
281      * @return A String.
282      */
283     public String list(Object list, String delim, String finaldelim)
284     {
285         return list(list, delim, finaldelim, null);
286     }
287 
288     /**
289      * Formats a specified property of collection or array of objects into the
290      * form "A&lt;delim&gt;B&lt;finaldelim&gt;C".
291      * 
292      * @param list A collection or array.
293      * @param delim A String.
294      * @param finaldelim A String.
295      * @param property An object property to format.
296      * @return A String.
297      */
298     public String list(Object list, String delim, String finaldelim,
299                        String property)
300     {
301         if (list == null)
302         {
303             return null;
304         }
305         if (list instanceof Collection)
306         {
307             return format((Collection) list, delim, finaldelim, property);
308         }
309         Collection items;
310         if (list.getClass().isArray())
311         {
312             int size = Array.getLength(list);
313             items = new ArrayList(size);
314             for (int i = 0; i < size; i++)
315             {
316                 items.add(Array.get(list, i));
317             }
318         }
319         else
320         {
321             items = Collections.singletonList(list);
322         }
323         return format(items, delim, finaldelim, property);
324     }
325 
326     /**
327      * Does the actual formatting of the collection.
328      */
329     protected String format(Collection list, String delim, String finaldelim,
330                             String property)
331     {
332         StringBuilder sb = new StringBuilder();
333         int size = list.size();
334         Iterator iterator = list.iterator();
335         for (int i = 0; i < size; i++)
336         {
337             if (property != null && property.length() > 0)
338             {
339                 sb.append(getProperty(iterator.next(), property));
340             }
341             else
342             {
343                 sb.append(iterator.next());
344             }
345             if (i < size - 2)
346             {
347                 sb.append(delim);
348             }
349             else if (i < size - 1)
350             {
351                 sb.append(finaldelim);
352             }
353         }
354         return sb.toString();
355     }
356 
357     /**
358      * @deprecated Will be unnecessary with Velocity 1.6
359      */
360     @Deprecated 
361     public String message(String format, Collection args)
362     {
363         return message(format, new Object[] { args });
364     }
365 
366     /**
367      * @deprecated Will be unnecessary with Velocity 1.6
368      */
369     @Deprecated 
370     public String message(String format, Object arg)
371     {
372         return message(format, new Object[] { arg });
373     }
374 
375     /**
376      * @deprecated Will be unnecessary with Velocity 1.6
377      */
378     @Deprecated 
379     public String message(String format, Object arg1, Object arg2)
380     {
381         return message(format, new Object[] { arg1, arg2 });
382     }
383 
384     /**
385      * Uses {@link MessageFormat} to format the specified String with
386      * the specified arguments. If there are no arguments, then the String
387      * is returned directly.  Please note that the format
388      * required here is quite different from that of
389      * {@link #printf(String,Object...)}.
390      *
391      * @since VelocityTools 2.0
392      */
393     public String message(String format, Object... args)
394     {
395         if (format == null)
396         {
397             return null;
398         }
399         if (args == null || args.length == 0)
400         {
401             return format;
402         }
403         else if (args.length == 1 && args[0] instanceof Collection)
404         {
405             Collection list = (Collection)args[0];
406             if (list.isEmpty())
407             {
408                 return format;
409             }
410             else
411             {
412                 args = list.toArray();
413             }
414         }
415         return MessageFormat.format(format, args);
416     }
417 
418     /**
419      * Uses {@link String#format(Locale,String,Object...} to format the specified String
420      * with the specified arguments.  Please note that the format
421      * required here is quite different from that of
422      * {@link #message(String,Object...)}.
423      *
424      * @see java.util.Formatter
425      * @since VelocityTools 2.0
426      */
427     public String printf(String format, Object... args)
428     {
429         if (format == null)
430         {
431             return null;
432         }
433         if (args == null || args.length == 0)
434         {
435             return format;
436         }
437         if (args.length == 1 && args[0] instanceof Collection)
438         {
439             Collection list = (Collection)args[0];
440             if (list.isEmpty())
441             {
442                 return format;
443             }
444             else
445             {
446                 args = list.toArray();
447             }
448         }
449         return String.format(getLocale(), format, args);
450     }
451 
452     /**
453      * Limits the string value of 'truncateMe' to the configured max length
454      * in characters (default is 30 characters).
455      * If the string gets curtailed, the configured suffix
456      * (default is "...") is used as the ending of the truncated string.
457      *
458      * @param truncateMe The value to be truncated.
459      * @return A String.
460      */
461     public String truncate(Object truncateMe)
462     {
463         return truncate(truncateMe, this.defaultTruncateLength);
464     }
465 
466     /**
467      * Limits the string value of 'truncateMe' to 'maxLength' characters.
468      * If the string gets curtailed, the configured suffix
469      * (default is "...") is used as the ending of the truncated string.
470      *
471      * @param maxLength An int with the maximum length.
472      * @param truncateMe The value to be truncated.
473      * @return A String.
474      */
475     public String truncate(Object truncateMe, int maxLength)
476     {
477         return truncate(truncateMe, maxLength, this.defaultTruncateSuffix);
478     }
479 
480     /**
481      * Limits the string value of 'truncateMe' to the configured max length
482      * in characters (default is 30 characters).
483      * If the string gets curtailed, the specified suffix
484      * is used as the ending of the truncated string.
485      *
486      * @param truncateMe The value to be truncated.
487      * @param suffix A String.
488      * @return A String.
489      */
490     public String truncate(Object truncateMe, String suffix)
491     {
492         return truncate(truncateMe, this.defaultTruncateLength, suffix);
493     }
494 
495     /**
496      * Limits the string value of 'truncateMe' to the specified max length in
497      * characters. If the string gets curtailed, the specified suffix is used as
498      * the ending of the truncated string.
499      * 
500      * @param truncateMe The value to be truncated.
501      * @param maxLength An int with the maximum length.
502      * @param suffix A String.
503      * @return A String.
504      */
505     public String truncate(Object truncateMe, int maxLength, String suffix)
506     {
507         return truncate(truncateMe, maxLength, suffix, defaultTruncateAtWord);
508     }
509 
510     /**
511      * Limits the string value of 'truncateMe' to the latest complete word
512      * within the specified maxLength. If the string gets curtailed, the
513      * specified suffix is used as the ending of the truncated string.
514      * 
515      * @param truncateMe The value to be truncated.
516      * @param maxLength An int with the maximum length.
517      * @param suffix A String.
518      * @param defaultTruncateAtWord Truncate at a word boundary if true.
519      * @return A String.
520      */
521     public String truncate(Object truncateMe, int maxLength, String suffix,
522                            boolean defaultTruncateAtWord)
523     {
524         if (truncateMe == null || maxLength <= 0)
525         {
526             return null;
527         }
528 
529         String string = String.valueOf(truncateMe);
530         if (string.length() <= maxLength)
531         {
532             return string;
533         }
534         if (suffix == null || maxLength - suffix.length() <= 0)
535         {
536             // either no need or no room for suffix
537             return string.substring(0, maxLength);
538         }
539         if (defaultTruncateAtWord)
540         {
541             // find the latest space within maxLength
542             int lastSpace = string.substring(0, maxLength - suffix.length() + 1)
543                             .lastIndexOf(" ");
544             if (lastSpace > suffix.length())
545             {
546                 return string.substring(0, lastSpace) + suffix;
547             }
548         }
549         // truncate to exact character and append suffix
550         return string.substring(0, maxLength - suffix.length()) + suffix;
551 
552     }
553 
554     /**
555      * Returns a string of spaces of the specified length.
556      * @param length the number of spaces to return
557      */
558     public String space(int length)
559     {
560         if (length < 0)
561         {
562             return null;
563         }
564 
565         StringBuilder space = new StringBuilder();
566         for (int i=0; i < length; i++)
567         {
568             space.append(' ');
569         }
570         return space.toString();
571     }
572 
573     /**
574      * Truncates or pads the string value of the specified object as necessary
575      * to ensure that the returned string's length equals the default cell size.
576      * @param obj the value to be put in the 'cell'
577      */
578     public String cell(Object obj)
579     {
580         return cell(obj, this.defaultCellLength);
581     }
582 
583     /**
584      * Truncates or pads the string value of the specified object as necessary
585      * to ensure that the returned string's length equals the specified cell size.
586      * @param obj the value to be put in the 'cell'
587      * @param cellsize the size of the cell into which the object must be placed
588      */
589     public String cell(Object obj, int cellsize)
590     {
591         return cell(obj, cellsize, this.defaultCellSuffix);
592     }
593 
594     /**
595      * Truncates or pads the string value of the specified object as necessary
596      * to ensure that the returned string's length equals the default cell size.
597      * If truncation is necessary, the specified suffix will replace the end of
598      * the string value to indicate that.
599      * @param obj the value to be put in the 'cell'
600      * @param suffix the suffix to put at the end of any values that need truncating
601      *               to indicate that they've been truncated
602      */
603     public String cell(Object obj, String suffix)
604     {
605         return cell(obj, this.defaultCellLength, suffix);
606     }
607 
608     /**
609      * Truncates or pads the string value of the specified object as necessary
610      * to ensure that the returned string's length equals the specified cell size.
611      * @param obj the value to be put in the 'cell'
612      * @param cellsize the size of the cell into which the object must be placed
613      * @param suffix the suffix to put at the end of any values that need truncating
614      *               to indicate that they've been truncated
615      */
616     public String cell(Object obj, int cellsize, String suffix)
617     {
618         if (obj == null || cellsize <= 0)
619         {
620             return null;
621         }
622 
623         String value = String.valueOf(obj);
624         if (value.length() == cellsize)
625         {
626             return value;
627         }
628         else if (value.length() > cellsize)
629         {
630             return truncate(value, cellsize, suffix);
631         }
632         else
633         {
634             return value + space(cellsize - value.length());
635         }    
636     }
637 
638     /**
639      * Changes the first character of the string value of the specified object
640      * to upper case and returns the resulting string.
641      *
642      * @param capitalizeMe The value to be capitalized.
643      */
644     public String capitalize(Object capitalizeMe)
645     {
646         if (capitalizeMe == null)
647         {
648             return null;
649         }
650 
651         String string = String.valueOf(capitalizeMe);
652         switch (string.length())
653         {
654             case 0:
655                 return string;
656             case 1:
657                 return string.toUpperCase();
658             default:
659                 StringBuilder out = new StringBuilder(string.length());
660                 out.append(string.substring(0,1).toUpperCase());
661                 out.append(string.substring(1, string.length()));
662                 return out.toString();
663         }
664     }
665 
666     /**
667      * Changes the first character of the string value of the specified object
668      * to lower case and returns the resulting string.
669      *
670      * @param uncapitalizeMe The value to be uncapitalized.
671      */
672     public String uncapitalize(Object uncapitalizeMe)
673     {
674         if (uncapitalizeMe == null)
675         {
676             return null;
677         }
678 
679         String string = String.valueOf(uncapitalizeMe);
680         switch (string.length())
681         {
682             case 0:
683                 return string;
684             case 1:
685                 return string.toLowerCase();
686             default:
687                 StringBuilder out = new StringBuilder(string.length());
688                 out.append(string.substring(0,1).toLowerCase());
689                 out.append(string.substring(1, string.length()));
690                 return out.toString();
691         }
692     }
693 
694     /**
695      * Returns a configured default value if specified value is null.
696      * @param checkMe
697      * @return a configured default value if the specified value is null.
698      */
699     public Object alt(Object checkMe)
700     {
701         return alt(checkMe, this.defaultAlternate);
702     }
703 
704     /**
705      * Returns the second argument if first argument specified is null.
706      * @param checkMe
707      * @param alternate
708      * @return the second argument if the first is null.
709      */
710     public Object alt(Object checkMe, Object alternate)
711     {
712         if (checkMe == null)
713         {
714             return alternate;
715         }
716         return checkMe;
717     }
718 
719     /**
720      * Inserts HTML line break tag (&lt;br /&gt;) in front of all newline
721      * characters of the string value of the specified object and returns the
722      * resulting string.
723      * @param obj
724      */
725     public String br(Object obj)
726     {
727         if (obj == null) 
728         {
729             return null;
730         }
731         else
732         {
733             return String.valueOf(obj).replaceAll("\n", "<br />\n");
734         }
735     }
736 
737     /**
738      * Removes HTML tags from the string value of the specified object and
739      * returns the resulting string.
740      * @param obj
741      */
742     public String stripTags(Object obj)
743     {
744         return stripTags(obj, defaultAllowedTags);
745     }
746 
747     /**
748      * Removes all not allowed HTML tags from the string value of the specified
749      * object and returns the resulting string.
750      * @param obj
751      * @param allowedTags An array of allowed tag names (i.e. "h1","br","img")
752      */
753     public String stripTags(Object obj, String... allowedTags)
754     {
755         if (obj == null)
756         {
757             return null;
758         }
759         
760         //build list of tags to be used in regex pattern
761         StringBuilder allowedTagList = new StringBuilder();
762         if (allowedTags != null)
763         {
764             for (String tag : allowedTags)
765             {
766                 if (tag !=null && tag.matches("[a-zA-Z0-9]+"))
767                 {
768                     if (allowedTagList.length() > 0)
769                     {
770                         allowedTagList.append("|");
771                     }
772                     allowedTagList.append(tag);
773                 }
774             }
775         }
776         String tagRule = "<[^>]*?>";
777         if (allowedTagList.length() > 0)
778         {
779             tagRule = "<(?!/?(" + allowedTagList.toString() + ")[\\s>/])[^>]*?>";
780         }
781         return Pattern.compile(tagRule, Pattern.CASE_INSENSITIVE)
782                 .matcher(String.valueOf(obj)).replaceAll("");
783     }
784 
785     /**
786      * Builds plural form of a passed word if 'value' is plural, otherwise
787      * returns 'singular'. Plural form is built using some basic English
788      * language rules for nouns which does not guarantee correct syntax of a
789      * result in all cases.
790      * @param value
791      * @param singular Singular form of a word.
792      */
793     public String plural(int value, String singular)
794     {
795         return plural(value, singular, null);
796     }
797 
798     /**
799      * Returns 'plural' parameter if passed 'value' is plural, otherwise
800      * 'singular' is returned.
801      * @param value
802      * @param singular Singular form of a word.
803      * @param plural Plural form of a word.
804      */
805     public String plural(int value, String singular, String plural)
806     {
807         if (value == 1 || value == -1)
808         {
809             return singular;
810         }
811         else if (plural != null)
812         {
813             return plural;
814         }
815         else if (singular == null || singular.length() == 0)
816         {
817             return singular;
818         }
819         else
820         {
821             //if the last letter is capital then we will append capital letters 
822             boolean isCapital = !singular.substring(singular.length() - 1)
823                                 .toLowerCase().equals(singular
824                                 .substring(singular.length() - 1));
825             
826             String word = singular.toLowerCase();
827             
828             if (word.endsWith("x") || word.endsWith("sh")
829                     || word.endsWith("ch") || word.endsWith("s"))
830             {
831                 return singular.concat(isCapital ? "ES" : "es");
832             }
833             else if (word.length() > 1
834                     && word.endsWith("y")
835                     && !word.substring(word.length() - 2, word.length() - 1)
836                             .matches("[aeiou]"))
837             {
838                 return singular.substring(0, singular.length() - 1)
839                         .concat(isCapital ? "IES" : "ies");
840             }
841             else
842             {
843                 return singular.concat(isCapital ? "S" : "s");
844             }
845         }
846     }
847 
848     /**
849      * Safely retrieves the specified property from the specified object.
850      * Subclasses that wish to perform more advanced, efficient, or just
851      * different property retrieval methods should override this method to do
852      * so.
853      */
854     protected Object getProperty(Object object, String property)
855     {
856         try
857         {
858             return PropertyUtils.getProperty(object, property);
859         }
860         catch (Exception e)
861         {
862             throw new IllegalArgumentException("Could not retrieve '"
863                     + property + "' from " + object + ": " + e);
864         }
865     }
866 
867     /**
868      * Returns the {@link Measurements} of the string value of the specified object.
869      */
870     public Measurements measure(Object measureMe)
871     {
872         if (measureMe == null)
873         {
874             return null;
875         }
876         return new Measurements(String.valueOf(measureMe));
877     }
878 
879 
880     /**
881      * Measures the dimensions of the string given to its constructor.
882      * Height is the number of lines in the string.
883      * Width is the number of characters in the longest line.
884      */
885     public static class Measurements
886     {
887         private int height;
888         private int width;
889 
890         public Measurements(String s)
891         {
892             String[] lines = s.split("\n");
893             height = lines.length;
894             for (String line : lines)
895             {
896                 if (line.length() > width)
897                 {
898                     width = line.length();
899                 }
900             }
901         }
902 
903         public int getHeight()
904         {
905             return height;
906         }
907 
908         public int getWidth()
909         {
910             return width;
911         }
912 
913         public String toString()
914         {
915             StringBuilder out = new StringBuilder(28);
916             out.append("{ height: ");
917             out.append(height);
918             out.append(", width: ");
919             out.append(width);
920             out.append(" }");
921             return out.toString();
922         }
923     }
924 
925 }