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.