package io.ebeaninternal.server.deploy;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.STreeProperty;
import java.sql.Types;
final class FormulaPropertyPath {
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg", "sum"};
private static final String DISTINCT_ = "distinct ";
private final BeanDescriptor<?> descriptor;
private final String formula;
private final String outerFunction;
private final String internalExpression;
private final ElPropertyDeploy firstProp;
private final String parsedAggregation;
private boolean countDistinct;
private String cast;
private String alias;
static STreeProperty create(BeanDescriptor<?> descriptor, String formula, String path) {
return new FormulaPropertyPath(descriptor, formula, path).build();
}
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula, String path) {
this.descriptor = descriptor;
this.formula = formula;
int openBracket = formula.indexOf('(');
int closeBracket = formula.lastIndexOf(')');
if (openBracket == -1 || closeBracket == -1) {
throw new IllegalStateException("Unable to parse formula [" + formula + "]");
}
outerFunction = formula.substring(0, openBracket).trim();
internalExpression = trimDistinct(formula.substring(openBracket + 1, closeBracket));
if (closeBracket < formula.length() - 1) {
parseSuffix(formula.substring(closeBracket + 1).trim());
}
DeployPropertyParser parser = descriptor.parser().setCatchFirst(true);
String parsed = parser.parse(internalExpression);
if (path != null) {
parsed = parsed.replace("${}", "${" + path + "}");
}
this.parsedAggregation = buildFormula(parsed);
this.firstProp = parser.getFirstProp();
}
private void parseSuffix(String suffix) {
String[] split = suffix.split(" ");
if (split.length == 1) {
if (split[0].startsWith("::")) {
cast = split[0].substring(2);
} else {
alias = split[0];
}
} else if (split.length == 2) {
cast = "as".equals(split[0]) ? null : split[0].substring(2);
alias = split[1];
} else if (split.length == 3) {
cast = split[0].substring(2);
alias = split[2];
}
}
private String trimDistinct(String propertyName) {
if (propertyName.startsWith(DISTINCT_)) {
countDistinct = true;
return propertyName.substring(DISTINCT_.length());
} else {
return propertyName;
}
}
String outerFunction() {
return outerFunction;
}
String internalExpression() {
return internalExpression;
}
String cast() {
return cast;
}
String alias() {
return alias;
}
STreeProperty build() {
if (cast != null) {
ScalarType<?> scalarType = descriptor.getScalarType(cast);
if (scalarType == null) {
throw new IllegalStateException("Unable to find scalarType for cast of [" + cast + "] on formula [" + formula + "] for type " + descriptor);
}
return create(scalarType);
}
if (isCount()) {
return create(descriptor.getScalarType(Types.BIGINT));
}
if (isConcat()) {
return create(descriptor.getScalarType(Types.VARCHAR));
}
if (firstProp == null) {
throw new IllegalStateException("unable to determine scalarType of formula [" + formula + "] for type " + descriptor + " - maybe use a cast like ::String ?");
}
final BeanProperty property = firstProp.getBeanProperty();
if (!property.isAssocId()) {
return create(property.getScalarType());
} else {
return createManyToOne(property);
}
}
private DynamicPropertyAggregationFormula create(ScalarType<?> scalarType) {
String logicalName = logicalName();
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), target(logicalName), alias);
}
private DynamicPropertyAggregationFormula createManyToOne(BeanProperty property) {
String logicalName = logicalName();
return new DynamicPropertyAggregationFormulaMTO((BeanPropertyAssocOne) property, logicalName, parsedAggregation, isAggregate(), target(logicalName), alias);
}
private BeanProperty target(String logicalName) {
return descriptor._findBeanProperty(logicalName);
}
private String logicalName() {
return (alias == null) ? internalExpression : alias;
}
private boolean isAggregate() {
for (String aggFunction : AGG_FUNCTIONS) {
if (aggFunction.equals(outerFunction)) {
return true;
}
}
return false;
}
private String buildFormula(String parsed) {
if (countDistinct) {
return "count(distinct " + parsed + ")";
} else {
return outerFunction + "(" + parsed + ")";
}
}
private boolean isCount() {
return outerFunction.equals("count");
}
private boolean isConcat() {
return outerFunction.equals("concat");
}
}