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