com.servicerocket.confluence.randombits.metadata.type.EvaluatedNumber Maven / Gradle / Ivy
package com.servicerocket.confluence.randombits.metadata.type;
import com.atlassian.confluence.core.ContentEntityObject;
import org.apache.commons.lang.StringUtils;
import com.servicerocket.confluence.randombits.metadata.MetadataManager;
import com.servicerocket.confluence.randombits.metadata.MetadataStorage;
import com.servicerocket.confluence.randombits.metadata.expression.RoundFunction;
import com.servicerocket.confluence.randombits.metadata.expression.TableFunctionParser;
import org.randombits.math.eval.Constant;
import org.randombits.math.eval.ParseException;
import org.randombits.math.eval.Parser;
import com.servicerocket.confluence.randombits.storage.EmptyStorage;
import com.servicerocket.confluence.randombits.storage.Storage;
import com.servicerocket.confluence.randombits.storage.StorageException;
import java.util.Map;
/**
* A mathematical expression that is calculated based on the Metadata from a particular
* data path of a {@link ContentEntityObject}.
*/
public class EvaluatedNumber extends Number implements Comparable {
private final ContentEntityObject content;
private final String dataPath;
private final String expression;
private Number value;
private final MetadataManager metadataManager;
public EvaluatedNumber( MetadataManager metadataManager, ContentEntityObject content, String dataPath, String expression ) {
this.content = content;
this.dataPath = dataPath;
this.expression = expression;
this.metadataManager = metadataManager;
}
public ContentEntityObject getContent() {
return content;
}
public String getDataPath() {
return dataPath;
}
public String getExpression() {
return expression;
}
private Number getValue() {
if ( value == null ) {
evaluate();
}
return value;
}
@Override
public int intValue() {
return getValue().intValue();
}
@Override
public long longValue() {
return getValue().longValue();
}
@Override
public float floatValue() {
return getValue().floatValue();
}
@Override
public double doubleValue() {
return getValue().doubleValue();
}
/**
* Resets the expression value. It will be recalculated next
* time the number value is requested, or when {@link #evaluate()}
* is called;
*/
public void reset() {
value = null;
}
/**
* Evaluates the expression in this instance.
*/
public void evaluate() {
String[] names = null;
Storage data = getData();
if ( data != null ) {
names = walkPath( data, dataPath );
}
value = recalculateValue( data, expression );
if ( data != null ) {
unwalkPath( data, names );
}
}
private Storage getData() {
if ( content != null )
return metadataManager.loadReadableData( content );
return new EmptyStorage();
}
private static void unwalkPath( Storage storage, String[] names ) {
if ( names != null ) {
for (String name : names) {
storage.closeBox();
}
}
}
private static String[] walkPath( Storage storage, String path ) {
if ( StringUtils.isNotBlank( path ) ) {
String[] names = path.split( "\\" + Storage.SEPARATOR );
if ( names.length > 0 ) {
for (String name : names) {
storage.openBox(name);
}
return names;
}
}
return null;
}
private static Number recalculateValue( Storage data, String expression ) {
Parser parser = new Parser();
parser.add( new RoundFunction() );
parser.add( new TableFunctionParser( data, TableFunctionParser.Type.SUM, MetadataStorage.ROW_COUNT_FIELD ) );
parser.add( new TableFunctionParser( data, TableFunctionParser.Type.AVERAGE, MetadataStorage.ROW_COUNT_FIELD ) );
String expr = processFields( expression, parser, data );
try {
return parser.parse( expr ).getVal();
} catch ( ParseException e ) {
return Double.NaN;
}
}
/**
* Parses the expression and adds field references as variables for
* evaluation.
*
* @param expr The expression.
* @param parser The expression parser.
* @param fields The fields to process.
* @return The modified expression.
*/
private static String processFields( String expr, Parser parser, Storage fields ) {
StringBuffer out = new StringBuffer();
String field, alias;
Map aliases = new java.util.HashMap();
int aliasCount = 0;
int start, end = -1;
for ( start = expr.indexOf( "${" ); start >= 0; start = expr.indexOf( "${", end ) ) {
// Append the last chunk of text
out.append( expr.substring( end + 1, start ) );
// Deal with the field/variable
end = expr.indexOf( "}", start );
field = expr.substring( start + 2, end );
alias = aliases.get( field );
if ( alias == null ) {
alias = "x" + aliasCount;
aliases.put( field, alias );
aliasCount++;
try {
Number numValue = null;
Object value = fields.getObject( field, null );
if ( value instanceof String ) {
try {
numValue = new Double( (String) value );
} catch ( NumberFormatException e ) {
// Do nothing...
}
}
if ( value instanceof Number )
numValue = (Number) value;
double doubleValue = numValue != null ? numValue.doubleValue() : 0.0;
parser.add( new Constant( alias, doubleValue ) );
// ctx.addMessage("Aliased '" + field + "' as '" + alias +
// "': " + value);
} catch ( StorageException e ) {
// ignore the bad value.
}
}
out.append( alias );
}
out.append( expr.substring( end + 1 ) );
// ctx.addMessage("Processed expression: " + out);
return out.toString();
}
@Override
public int hashCode() {
return getValue().hashCode() + 11;
}
@Override
public boolean equals( Object o ) {
if ( o instanceof Number ) {
return getValue().equals( o );
}
return false;
}
@Override
public String toString() {
return getValue().toString();
}
public int compareTo( EvaluatedNumber evaluatedNumber ) {
double self = getValue().doubleValue();
double other = evaluatedNumber.doubleValue();
return self < other ? -1 : self > other ? 1 : 0;
}
}