001    package org.nakedobjects.applib.util;
002    
003    import java.lang.reflect.InvocationTargetException;
004    import java.lang.reflect.Method;
005    
006    /**
007     * Title buffer is a utility class to help produce titles for objects without having to add lots of guard
008     * code. It provides two basic method: one to concatenate a title to the buffer; another to append a title
009     * with a joiner string, taking care adding in necessary spaces. The benefits of using this class is that null
010     * references are safely ignored (rather than appearing as 'null'), and joiners (a space by default) are only
011     * added when needed.
012     */
013    public class TitleBuffer {
014        private static final String SPACE = " ";
015    
016        /**
017         * Determines if the specified object's title (from its <code>toString</code> method) is empty. Will
018         * return true if either: the specified reference is null; the object's <code>toString</code> method
019         * returns null; or if the <code>toString</code> returns an empty string.
020         */
021        public static boolean isEmpty(final Object object) {
022            String title = titleFor(object);
023            return title == null ||  title.equals("");
024        }
025    
026        /**
027         * Reflectively run the <tt>String title()</tt> method if it exists, else fall back to the <tt>toString()</tt> method.
028         */
029        private static String titleFor(Object object) {
030            if(object == null) {return null;} 
031            else {
032                Method method;
033                try {
034                    method = object.getClass().getMethod("title", new Class[0]);
035                    return (String) method.invoke(object, new Object[0]);
036                } catch (SecurityException e) {
037                    throw new TitleBufferException(e);
038                } catch (NoSuchMethodException e) {
039                    return object.toString();
040                } catch (IllegalArgumentException e) {
041                    throw new TitleBufferException(e);
042                } catch (IllegalAccessException e) {
043                    throw new TitleBufferException(e);
044                } catch (InvocationTargetException e) {
045                    throw new TitleBufferException(e);
046                }
047            }
048        }
049    
050        /**
051         * Determines if the specified text is empty. Will return true if either: the specified reference is null;
052         * or if the reference is an empty string.
053         */
054        public static boolean isEmpty(final String text) {
055            return text == null || text.equals("");
056        }
057    
058        private final StringBuffer title;
059    
060        /**
061         * Creates a new, empty, title object.
062         */
063        public TitleBuffer() {
064            title = new StringBuffer();
065        }
066    
067        /**
068         * Creates a new title object, containing the title of the specified object.
069         */
070        public TitleBuffer(final Object object) {
071            this();
072            concat(object);
073        }
074    
075        /**
076         * Creates a new title object, containing the title of the specified object.
077         */
078        public TitleBuffer(final Object object, final String defaultTitle) {
079            this();
080            if (isEmpty(object)) {
081                concat(defaultTitle);
082            } else {
083                concat(object);
084            }
085        }
086    
087        /**
088         * Creates a new title object, containing the specified text.
089         */
090        public TitleBuffer(final String text) {
091            this();
092            concat(text);
093        }
094    
095        /**
096         * 
097         */
098        public TitleBuffer append(final int number) {
099            append(String.valueOf(number));
100            return this;
101        }
102    
103        /**
104         * Append the title of the specified object.
105         */
106        public TitleBuffer append(final Object object) {
107            if (!isEmpty(object)) {
108                appendWithSpace(object);
109            }
110            return this;
111        }
112    
113        /**
114         * Appends the title of the specified object, or the specified text if the objects title is null or empty.
115         * Prepends a space if there is already some text in this title object.
116         * 
117         * @param object
118         *            the object whose title is to be appended to this title.
119         * @param defaultValue
120         *            a textual value to be used if the object's title is null or empty.
121         * @return a reference to the called object (itself).
122         */
123        public TitleBuffer append(final Object object, final String defaultValue) {
124            if (!isEmpty(object)) {
125                appendWithSpace(object);
126            } else {
127                appendWithSpace(defaultValue);
128            }
129            return this;
130        }
131    
132        /**
133         * Appends a space (if there is already some text in this title object) and then the specified text.
134         * 
135         * @return a reference to the called object (itself).
136         */
137        public TitleBuffer append(final String text) {
138            if (!isEmpty(text)) {
139                appendWithSpace(text);
140            }
141            return this;
142        }
143    
144        /**
145         * Appends the joining string and the title of the specified object (from its <code>toString</code>
146         * method). If the object is empty then nothing will be appended.
147         * 
148         * @see #isEmpty(Object)
149         */
150        public TitleBuffer append(final String joiner, final Object object) {
151            if (!isEmpty(object)) {
152                appendJoiner(joiner);
153                appendWithSpace(object);
154            }
155            return this;
156        }
157    
158        /**
159         * Append the <code>joiner</code> text, a space, and the title of the specified naked object (<code>object</code>)
160         * (got by calling the objects title() method) to the text of this TitleString object. If the title of the
161         * specified object is null then use the <code>defaultValue</code> text. If both the objects title and
162         * the default value are null or equate to a zero-length string then no text will be appended ; not even
163         * the joiner text.
164         * 
165         * @param joiner
166         *            text to append before the title
167         * @param object
168         *            object whose title needs to be appended
169         * @param defaultTitle
170         *            the text to use if the the object's title is null.
171         * @return a reference to the called object (itself).
172         */
173        public TitleBuffer append(final String joiner, final Object object, final String defaultTitle) {
174            appendJoiner(joiner);
175            if (!isEmpty(object)) {
176                appendWithSpace(object);
177            } else {
178                appendWithSpace(defaultTitle);
179            }
180            return this;
181        }
182    
183        /**
184         * Appends the joiner text, a space, and the text to the text of this TitleString object. If no text yet
185         * exists in the object then the joiner text and space are omitted.
186         * 
187         * @return a reference to the called object (itself).
188         */
189        public TitleBuffer append(final String joiner, final String text) {
190            if (!isEmpty(text)) {
191                appendJoiner(joiner);
192                appendWithSpace(text);
193            }
194            return this;
195        }
196    
197        private void appendJoiner(final String joiner) {
198            if (title.length() > 0) {
199                title.append(joiner);
200            }
201        }
202    
203        /**
204         * Append a space to the text of this TitleString object if, and only if, there is some existing text
205         * i.e., a space is only added to existing text and will not create a text entry consisting of only one
206         * space.
207         * 
208         * @return a reference to the called object (itself).
209         */
210        public TitleBuffer appendSpace() {
211            if (title.length() > 0) {
212                title.append(SPACE);
213            }
214            return this;
215        }
216    
217        private void appendWithSpace(final Object object) {
218            appendSpace();
219            title.append(titleFor(object));
220        }
221    
222        /**
223         * Concatenate the the title value (the result of calling an objects label() method) to this TitleString
224         * object. If the value is null the no text is added.
225         * 
226         * @param object
227         *            the naked object to get a title from
228         * @return a reference to the called object (itself).
229         */
230        public final TitleBuffer concat(final Object object) {
231            concat(object, "");
232            return this;
233        }
234    
235        /**
236         * Concatenate the the title value (the result of calling an objects label() method), or the specified
237         * default value if the title is equal to null or is empty, to this TitleString object.
238         * 
239         * @param object
240         *            the naked object to get a title from
241         * @param defaultValue
242         *            the default text to use when the naked object is null
243         * @return a reference to the called object (itself).
244         */
245        public final TitleBuffer concat(final Object object, final String defaultValue) {
246            if (isEmpty(object)) {
247                title.append(defaultValue);
248            } else {
249                title.append(titleFor(object));
250            }
251    
252            return this;
253        }
254    
255        /**
256         * Concatenate the specified text on to the end of the text of this TitleString.
257         * 
258         * @param text
259         *            text to append
260         * @return a reference to the called object (itself).
261         */
262        public final TitleBuffer concat(final String text) {
263            title.append(text);
264            return this;
265        }
266    
267        public final TitleBuffer concat(final String joiner, final Object object) {
268            if (!isEmpty(object)) {
269                appendJoiner(joiner);
270                concat(object, "");
271            }
272            return this;
273        }
274    
275        public final TitleBuffer concat(final String joiner, final Object object, final String defaultValue) {
276            if (isEmpty(object)) {
277                appendJoiner(joiner);
278                title.append(defaultValue);
279            } else {
280                appendJoiner(joiner);
281                title.append(titleFor(object));
282            }
283            return this;
284        }
285    
286        /**
287         * Returns a String that represents the value of this object.
288         */
289        @Override
290        public String toString() {
291            return title.toString();
292        }
293    
294        /**
295         * Truncates this title so it has a maximum number of words. Spaces are used to determine words, thus two
296         * spaces in a title will cause two words to be mistakenly identified.
297         * 
298         * @param noWords
299         *            the number of words to show
300         * @return a reference to the called object (itself).
301         */
302        public TitleBuffer truncate(final int noWords) {
303            if (noWords < 1) {
304                throw new IllegalArgumentException("Truncation must be to one or more words");
305            }
306            int pos = 0;
307            int spaces = 0;
308    
309            while (pos < title.length() && spaces < noWords) {
310                if (title.charAt(pos) == ' ') {
311                    spaces++;
312                }
313                pos++;
314            }
315            if (pos < title.length()) {
316                title.setLength(pos - 1); // string.delete(pos - 1, string.length());
317                title.append("...");
318            }
319            return this;
320        }
321    
322    }
323    
324    // Copyright (c) Naked Objects Group Ltd.