package simple.escp.placeholder;
import simple.escp.data.DataSource;
import simple.escp.exception.InvalidPlaceholder;
import simple.escp.util.StringUtil;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.logging.Logger;
/**
* <code>Placeholder</code> represent a placeholder in template, such as <code>${name}</code> or
* <code>{{salary * 0.5}}</code>.
*
* <p>Placeholder may have an optional formatting, such as <code>${salary:currency}</code>. The following
* format options are available on number value: <code>number</code>, <code>integer</code> and
* <code>currency</code>. The following options are available on date value: <code>date_full</code>,
* <code>date_long</code>, <code>date_medium</code>, and <code>date_short</code>.
*
* <p>All value types support a number as options that will limit the result of this placeholder if number
* of resulting characters more than this number. For example: <code>${salary:currency:10}</code> will always take
* 10 characters. If the actual value is greater than the width, it will be truncated. If the actual value is
* less than the width, spaces will be appended to it.
*
* <p>If placeholders has more than one part separated by semicolon (<code>:</code>), the first part should always
* be name of the placeholder.
*
*/
public abstract class Placeholder {
private static final Logger LOG = Logger.getLogger("simple.escp");
protected String text;
protected Format format;
protected int width = 0;
protected boolean sum;
protected boolean count;
protected StringUtil.ALIGNMENT alignment;
/**
* Create a new instance of placeholder.
*
* @param text a string that defines this placeholder.
*/
protected Placeholder(String text) {
this.text = text.trim();
}
/**
* Get the text of this placeholder. All placeholder will be identified in template
* by their text. For example, placeholder text for <code>${name}</code> is <code>name</code>.
*
* @return text of this placeholder.
*/
public String getText() {
return text;
}
/**
* Set the text of this placeholder.
*
* @param text the text for this placeholder.
*/
public void setText(String text) {
this.text = text;
}
/**
* Retrieve the maximal width allowed for the value of this placeholder.
*
* @return maximal number of characters for this placeholder.
*/
public int getWidth() {
return width;
}
/**
* Set the maximal number of characters for this placeholder.
*
* @param width new maximal number of characters (width) for this placeholder, or <code>0</code> if it is
* unlimited.
*/
public void setWidth(int width) {
this.width = width;
}
/**
* Get a <code>Format</code> for this placeholder.
*
* @return an instance of <code>Format</code> for this placeholder. If no <code>format</code> is defined,
* this method will return <code>null</code>.
*/
public Format getFormat() {
return format;
}
/**
* Set a new <code>Format</code> for this placeholder.
*
* @param format new format for this placeholder.
*/
public void setFormat(Format format) {
this.format = format;
}
/**
* Determine if this placeholder is for displaying sum of value.
*
* @return <code>true</code> if this placeholder is for displaying sum of value.
*/
public boolean isSum() {
return sum;
}
/**
* Set this placeholder to display sum of value instead of the value.
*
* @param sum if <code>true</code>, this placeholder will return sum of value.
*/
public void setSum(boolean sum) {
this.sum = sum;
}
/**
* Determine if this placeholder is for displaying count of value.
*
* @return <code>true</code> if this placeholder is for displaying count of value.
*/
public boolean isCount() {
return count;
}
/**
* Set this placeholder to display count of value instead of the value.
*
* @param count if <code>true</code>, this placeholder will return count of value.
*/
public void setCount(boolean count) {
this.count = count;
}
/**
* Get the alignment for this placeholder.
*
* @return alignment for this placeholder or <code>null</code> if no alignment is specified for this
* placeholder.
*/
public StringUtil.ALIGNMENT getAlignment() {
return alignment;
}
/**
* Set the new alignment for this placeholder.
*
* @param alignment the new alignment for this placeholder.
*/
public void setAlignment(StringUtil.ALIGNMENT alignment) {
this.alignment = alignment;
}
/**
* Calculate the sum of <code>Collection</code>.
*
* @param value a <code>Collection</code> to sum.
* @return sum of all values in the <code>Collection</code>.
*/
private BigDecimal getSumValue(Collection value) {
BigDecimal result = BigDecimal.ZERO;
for (Object v : value) {
if (v instanceof Number) {
result = result.add(BigDecimal.valueOf(((Number) v).doubleValue()));
} else {
throw new IllegalArgumentException("sum operation require number: " + v);
}
}
return result;
}
/**
* Calculate the count of elements in a <code>Collection</code>.
*
* @param value a <code>Collection</code> to count for.
* @return count of elements inside this <code>Collection</code>.
*/
public Object getCountValue(Collection value) {
return value.size();
}
/**
* Get a formatted version, including width limit, of a value.
*
* @param value the value passed to this placehoder.
* @return the formatted value. If <code>value</code> is formatted, it will be returned as <code>String</code>.
* If no formatting is specified for this placeholder, the <code>value</code> will be returned as is.
*/
public Object getFormatted(Object value) {
Object result = value;
LOG.fine("Formatting [" + value + "]");
if (value != null) {
if (isSum()) {
LOG.fine("Calculating sum for [" + value + "]");
if (!(value instanceof Collection)) {
LOG.warning("Can't calculate sum for [" + value + "] because it is not a Collection.");
throw new InvalidPlaceholder("Expected collection for placeholder [" + getText() + "] for " +
"sum operation but received value [" + value + "].");
} else {
result = getSumValue((Collection) value);
}
} else if (isCount()) {
LOG.fine("Calculating count for [" + value + "]");
if (!(value instanceof Collection)) {
LOG.warning("Can't calculate count for [" + value + "] because it is not a Collection.");
throw new InvalidPlaceholder("Expected collection for placeholder [" + getText() + "] for " +
"count operation but received value [" + value + "].");
} else {
result = getCountValue((Collection) value);
}
}
if (getFormat() != null) {
try {
LOG.fine("Formatting [" + result + "] as [" + getFormat() + "]");
result = getFormat().format(result);
} catch (IllegalArgumentException e) {
LOG.warning("Can't format [" + result + "] as [" + getFormat() + "]");
throw new InvalidPlaceholder("Can't format value [" + result + "] for placeholder [" +
getText() + "].", e);
}
}
}
if (getWidth() > 0) {
result = (result != null) ? result : "";
if (getAlignment() == null) {
LOG.fine("Left-align for [" + result + "] in width [" + getWidth() + "]");
result = StringUtil.alignLeft(result.toString(), getWidth());
} else {
LOG.fine(getAlignment() + " for [" + result + "] in width [" + getWidth() + "]");
result = StringUtil.align(result.toString(), getWidth(), getAlignment());
}
}
return (result != null) ? result : "";
}
/**
* Parse aggregation formula such as <code>"sum"</code> and <code>"count"</code> in placeholder text.
*
* @param text part of text for this placeholder.
*/
protected void parseFormula(String text) {
if ("sum".equals(text)) {
setSum(true);
} else if ("count".equals(text)) {
setCount(true);
}
}
/**
* Parse formatter such as <code>number</code>, <code>date_full</code>, etc.
*
* @param text part of text for this placeholder.
*/
protected void parseFormatter(String text) {
if ("number".equals(text)) {
format = NumberFormat.getNumberInstance();
} else if ("integer".equals(text)) {
format = NumberFormat.getIntegerInstance();
} else if ("currency".equals(text)) {
format = NumberFormat.getCurrencyInstance();
} else if ("date_full".equals(text)) {
format = DateFormat.getDateInstance(DateFormat.FULL);
} else if ("date_long".equals(text)) {
format = DateFormat.getDateInstance(DateFormat.LONG);
} else if ("date_medium".equals(text)) {
format = DateFormat.getDateInstance(DateFormat.MEDIUM);
} else if ("date_short".equals(text)) {
format = DateFormat.getDateInstance(DateFormat.SHORT);
}
}
/**
* Parse width for a placeholder. By default, <code>width</code> is 0 and no restriction will be applied.
*
* @param text part of text for this placeholder.
*/
protected void parseWidth(String text) {
try {
width = Integer.valueOf(text);
} catch (NumberFormatException e) {
LOG.fine("Can't convert [" + text + "] to number.");
}
}
/**
* Parse alignment for this placeholder, such as <code>"left"</code>, <code>"right"</code>, or
* <code>"center"</code>.
*
* @param text part of text for this placeholder.
*/
protected void parseAlignment(String text) {
if ("left".equals(text)) {
setAlignment(StringUtil.ALIGNMENT.LEFT);
} else if ("right".equals(text)) {
setAlignment(StringUtil.ALIGNMENT.RIGHT);
} else if ("center".equals(text)) {
setAlignment(StringUtil.ALIGNMENT.CENTER);
}
}
/**
* Parse an expression in placeholder expression. The expression should be broken into multiple keywords
* that are stored in <code>text</code>.
*
* @param text an array of string that represent keywords that should be parsed.
*/
protected void parseText(String[] text) {
for (String part: text) {
part = part.trim();
LOG.fine("Processing part [" + part + "]");
parseFormula(part);
parseFormatter(part);
parseWidth(part);
parseAlignment(part);
}
}
/**
* Retrieve a value from specified <code>DataSource</code>.
*
* @param dataSources the data sources from where this placeholder retrieves its value.
* @return the value for the member name.
* @throws simple.escp.exception.InvalidPlaceholder if can't find the value for the member name in
* data source.
*/
public abstract Object getValue(DataSource[] dataSources);
/**
* Retrieve a value for a <code>Placeholder</code> in form of <code>String</code>.
*
* @param dataSources the data sources from where this placeholder retrieves its value.
* @return the value for the <code>placeholder</code> in form of <code>String</code>.
* @throws simple.escp.exception.InvalidPlaceholder if can't find the value for <code>placeholder</code> is
* data source.
*/
public String getValueAsString(DataSource[] dataSources) {
return getFormattedValue(dataSources).toString();
}
/**
* Get a formatted version of {@link #getValue(simple.escp.data.DataSource[])} .
*
* @param dataSources the data sources from where this placeholder retrieves its value.
* @return the formatted value. If <code>value</code> is formatted, it will be returned as <code>String</code>.
* If no formatting is specified for this placeholder, the <code>value</code> will be returned as is.
*/
public Object getFormattedValue(DataSource[] dataSources) {
return getFormatted(getValue(dataSources));
}
}