/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.isis.applib.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Title buffer is a utility class to help produce titles for objects without
* having to add lots of guard code. It provides two basic method: one to
* concatenate a title to the buffer; another to append a title with a joiner
* string, taking care adding in necessary spaces. The benefits of using this
* class is that null references are safely ignored (rather than appearing as
* 'null'), and joiners (a space by default) are only added when needed.
*/
public class TitleBuffer {
private static final String SPACE = " ";
public static final Class[] NO_PARAMETER_TYPES = new Class[0];
public static final Object[] NO_ARGUMENTS = new Object[0];
/**
* Determines if the specified object's title is empty (or null).
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*/
public static boolean isEmpty(final Object object) {
final String title = titleFor(object);
return isEmpty(title);
}
/**
* Reflectively run the <tt>String title()</tt> method if it exists, else
* fall back to the <tt>toString()</tt> method.
*/
private static String titleFor(final Object object) {
if (object == null) {
return null;
}
if(object instanceof String) {
return object.toString();
}
try {
Method method = object.getClass().getMethod("title", NO_PARAMETER_TYPES);
return (String) method.invoke(object, NO_ARGUMENTS);
} catch (final SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new TitleBufferException(e);
} catch (final NoSuchMethodException e) {
return object.toString();
}
}
/**
* Determines if the specified text is empty. Will return true if either:
* the specified reference is null; or if the reference is an empty string.
*/
public static boolean isEmpty(final String text) {
return text == null || text.equals("");
}
private final StringBuilder title;
/**
* Creates a new, empty, {@link org.apache.isis.applib.util.TitleBuffer}.
*/
public TitleBuffer() {
title = new StringBuilder();
}
/**
* Creates a new {@link org.apache.isis.applib.util.TitleBuffer}, containing the title of the specified object.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*/
public TitleBuffer(final Object object) {
this();
concat(object);
}
/**
* Creates a new title object, containing the title of the specified object.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*/
public TitleBuffer(final Object object, final String defaultTitle) {
this();
String title = titleFor(object);
if (isEmpty(title)) {
concat(defaultTitle);
} else {
concat(title);
}
}
/**
* Creates a new title object, containing the specified text.
*/
public TitleBuffer(final String text) {
this();
concat(text);
}
/**
*
*/
public TitleBuffer append(final int number) {
append(String.valueOf(number));
return this;
}
/**
* Append the title of the specified object to this {@link org.apache.isis.applib.util.TitleBuffer}.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*/
public TitleBuffer append(final Object object) {
String title = titleFor(object);
if (!isEmpty(title)) {
appendWithSpace(title);
}
return this;
}
/**
* Appends the title of the specified object, or the specified text if the
* objects title is null or empty. Prepends a space if there is already some
* text in this title object.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*
* @param object
* the object whose title is to be appended to this title.
* @param defaultValue
* a textual value to be used if the object's title is null or
* empty.
* @return a reference to the called object (itself).
*/
public TitleBuffer append(final Object object, final String defaultValue) {
String title = titleFor(object);
if (!isEmpty(title)) {
appendWithSpace(title);
} else {
appendWithSpace(defaultValue);
}
return this;
}
/**
* Appends a space (if there is already some text in this title object) and
* then the specified text.
*
* @return a reference to the called object (itself).
*/
public TitleBuffer append(final String text) {
if (!isEmpty(text)) {
appendWithSpace(text);
}
return this;
}
/**
* Appends the joining string and the title of the specified object. If the object is empty then nothing
* will be appended.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*
* @see #isEmpty(Object)
*/
public TitleBuffer append(final String joiner, final Object object) {
String title = titleFor(object);
if (!isEmpty(title)) {
appendJoiner(joiner);
appendWithSpace(title);
}
return this;
}
/**
* Append the <code>joiner</code> text, a space, and the title of the
* specified object to the text of this {@link org.apache.isis.applib.util.TitleBuffer}. If the title of
* the specified object is null then use the <code>defaultValue</code> text.
* If both the objects title and the default value are null or equate to a
* zero-length string then no text will be appended ; not even the joiner
* text.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*
* @param joiner
* text to append before the title
* @param object
* object whose title needs to be appended
* @param defaultTitle
* the text to use if the the object's title is null.
* @return a reference to the called object (itself).
*/
public TitleBuffer append(final String joiner, final Object object, final String defaultTitle) {
appendJoiner(joiner);
String title = titleFor(object);
if (!isEmpty(title)) {
appendWithSpace(title);
} else {
appendWithSpace(defaultTitle);
}
return this;
}
/**
* Appends the joiner text, a space, and the text to the text of this
* {@link org.apache.isis.applib.util.TitleBuffer}. If no text yet exists in the object then the joiner
* text and space are omitted.
*
* @return a reference to the called object (itself).
*/
public TitleBuffer append(final String joiner, final String text) {
if (!isEmpty(text)) {
appendJoiner(joiner);
appendWithSpace(text);
}
return this;
}
private void appendJoiner(final String joiner) {
if (title.length() > 0) {
title.append(joiner);
}
}
/**
* Append a space to the text of this TitleString object if, and only if,
* there is some existing text i.e., a space is only added to existing text
* and will not create a text entry consisting of only one space.
*
* @return a reference to the called object (itself).
*/
public TitleBuffer appendSpace() {
if (title.length() > 0) {
title.append(SPACE);
}
return this;
}
private void appendWithSpace(final Object object) {
appendSpace();
title.append(titleFor(object));
}
/**
* Concatenate the the title value (the result of calling an objects label()
* method) to this TitleString object. If the value is null the no text is
* added.
*
* @param object
* the ObjectAdapter to get a title from
* @return a reference to the called object (itself).
*/
public final TitleBuffer concat(final Object object) {
concat(object, "");
return this;
}
/**
* Concatenate the title of the object value or the specified default value if the title is equal to null or
* is empty, to this {@link org.apache.isis.applib.util.TitleBuffer}.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*
* @param object
* the object to get a title from
* @param defaultValue
* the default text to use when the object is null/empty
*
* @return a reference to the called object (itself).
*/
public final TitleBuffer concat(final Object object, final String defaultValue) {
String title = titleFor(object);
if (isEmpty(title)) {
this.title.append(defaultValue);
} else {
this.title.append(title);
}
return this;
}
/**
* Concatenate the specified text on to the end of the text of this
* {@link org.apache.isis.applib.util.TitleBuffer}.
*
* @param text
* text to append
* @return a reference to the called object (itself).
*/
public final TitleBuffer concat(final String text) {
title.append(text);
return this;
}
/**
* Concatenate the joiner text and the text to the text of this {@link org.apache.isis.applib.util.TitleBuffer}
* object. If no text yet exists in the object then the joiner text is
* omitted.
*
* @return a reference to the called object (itself).
*/
public TitleBuffer concat(final String joiner, final String text) {
if (!isEmpty(text)) {
appendJoiner(joiner);
title.append(text);
}
return this;
}
/**
* Concatenate the joiner text and the title of the object to the text of
* this {@link org.apache.isis.applib.util.TitleBuffer}. If no object yet exists in the object then the
* joiner text is omitted.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
*
* @return a reference to the called object (itself).
*/
public final TitleBuffer concat(final String joiner, final Object object) {
String title = titleFor(object);
if (!isEmpty(title)) {
appendJoiner(joiner);
concat(title, "");
}
return this;
}
/**
* Concatenate the joiner text and the title of the object to the text of
* this {@link org.apache.isis.applib.util.TitleBuffer} object. If no object yet exists in the object then
* defaultValue is used instead.
*
* <p>
* Note: this method only obtains the title using either <tt>title()</tt> or <tt>toString()</tt>; it doesn't
* honour other mechanisms for specifying the title, such as {@link org.apache.isis.applib.annotation.Title}
* annotation. If that functionality is required, first call
* {@link org.apache.isis.applib.DomainObjectContainer#titleOf(Object)} on the object and pass in the resultant
* string.
* </p>
* @return a reference to the called object (itself).
*/
public final TitleBuffer concat(final String joiner, final Object object, final String defaultValue) {
String title = titleFor(object);
if (isEmpty(title)) {
appendJoiner(joiner);
this.title.append(defaultValue);
} else {
appendJoiner(joiner);
this.title.append(title);
}
return this;
}
/**
* Returns a String that represents the value of this object.
*/
@Override
public String toString() {
return title.toString();
}
/**
* Truncates this title so it has a maximum number of words. Spaces are used
* to determine words, thus two spaces in a title will cause two words to be
* mistakenly identified.
*
* @param noWords
* the number of words to show
* @return a reference to the called object (itself).
*/
public TitleBuffer truncate(final int noWords) {
if (noWords < 1) {
throw new IllegalArgumentException("Truncation must be to one or more words");
}
int pos = 0;
int spaces = 0;
while (pos < title.length() && spaces < noWords) {
if (title.charAt(pos) == ' ') {
spaces++;
}
pos++;
}
if (pos < title.length()) {
title.setLength(pos - 1); // string.delete(pos - 1,
// string.length());
title.append("...");
}
return this;
}
}