/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZooDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.jdo.impl;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import org.zoodb.internal.SerializerTools.PRIMITIVE;
import org.zoodb.internal.ZooClassDef;
import org.zoodb.internal.ZooFieldDef;
import org.zoodb.internal.util.DBLogger;
/**
* Processes query results.
*
* See Jdo 2.2 spec 14.6.9.
*
* @author Tilmann Zaeschke
*
*/
class QueryResultProcessor {
private final ArrayList<Item> items = new ArrayList<Item>();
private boolean isProjection = false;
private static abstract class Item {
ZooFieldDef field;
Field jField;
Class<?> resultClass;
boolean isFloat;
void setField(ZooFieldDef field, Class<?> resultClass) {
this.field = field;
if (field.getJavaField() == null) {
field.getDeclaringType().associateJavaTypes();
}
this.jField = field.getJavaField();
this.resultClass = resultClass;
if (resultClass == null) {
this.resultClass = jField.getType();
}
isFloat = field.getPrimitiveType() == PRIMITIVE.FLOAT ||
field.getPrimitiveType() == PRIMITIVE.DOUBLE;
}
protected Object getValue(Object o) {
try {
return jField.get(o);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected double getFloat(Object o) {
try {
switch (field.getPrimitiveType()) {
case DOUBLE: return jField.getDouble(o);
case FLOAT: return jField.getFloat(o);
default:
throw new UnsupportedOperationException(field.getPrimitiveType().name());
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected long getInt(Object o) {
try {
switch (field.getPrimitiveType()) {
case BYTE: return jField.getByte(o);
case CHAR: return jField.getChar(o);
case INT: return jField.getInt(o);
case LONG: return jField.getLong(o);
case SHORT: return jField.getShort(o);
default:
throw new UnsupportedOperationException(field.getPrimitiveType().name());
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Object toFloat(double d) {
switch (field.getPrimitiveType()) {
case DOUBLE: return (double)d;
case FLOAT: return (float)d;
default:
throw new UnsupportedOperationException(field.getPrimitiveType().name());
}
}
Object toInt(long l) {
switch (field.getPrimitiveType()) {
//case BOOLEAN: return (boolean)avg;
case BYTE: return (byte)l;
case CHAR: return (char)l;
case DOUBLE: return (double)l;
case FLOAT: return (float)l;
case INT: return (int)l;
case LONG: return (long)l;
case SHORT: return(short)l;
default:
throw new UnsupportedOperationException(field.getPrimitiveType().name());
}
}
abstract void add(Object o);
abstract Object result();
}
private static class AVG extends Item {
private double d;
private long l;
long n;
@Override
void add(Object o) {
n++;
if (isFloat) {
d += getFloat(o);
} else {
l += getInt(o);
}
}
@Override
Object result() {
if (isFloat) {
double avg = d/(double)n;
return toFloat(avg);
} else {
long avg = l/n;
return toInt(avg);
}
}
}
private static class MAX extends Item {
private double d = Double.NEGATIVE_INFINITY;
private long l = Long.MIN_VALUE;
@Override
void add(Object o) {
if (isFloat) {
double d2 = getFloat(o);
if (d2 > d) {
d = d2;
}
} else {
long i2 = getInt(o);
if (i2 > l) {
l = i2;
}
}
}
@Override
Object result() {
if (isFloat) {
return toFloat(d);
} else {
return toInt(l);
}
}
}
private static class MIN extends Item {
private double d = Double.MAX_VALUE;
private long l = Long.MAX_VALUE;
@Override
void add(Object o) {
if (isFloat) {
double d2 = getFloat(o);
if (d2 < d) {
d = d2;
}
} else {
long i2 = getInt(o);
if (i2 < l) {
l = i2;
}
}
}
@Override
Object result() {
if (isFloat) {
return toFloat(d);
} else {
return toInt(l);
}
}
}
private static class SUM extends Item {
private double d;
private long l;
@Override
void add(Object o) {
if (isFloat) {
d += getFloat(o);
} else {
l += getInt(o);
}
}
@Override
Object result() {
if (isFloat) {
return d;
} else {
return l;
}
}
}
private static class COUNT extends Item {
private long n = 0;
@Override
void add(Object o) {
n++;
}
@Override
Object result() {
return n;
}
}
private static class FIELD extends Item {
private Object ret = null;
@Override
void add(Object o) {
ret = getValue(o);
}
@Override
Object result() {
return ret;
}
}
/**
*
* @param data For example: "avg(salary), sum(salary)". min, max, avg, sum, count
* @param candCls
* @param candClsDef
*/
QueryResultProcessor(String data, Class<?> candCls, ZooClassDef candClsDef,
Class<?> resultClass) {
data = data.trim();
while (data.length() > 0) {
Item item;
if (data.startsWith("avg") || data.startsWith("AVG")) {
item = new AVG();
data = data.substring(3);
} else if (data.startsWith("max") || data.startsWith("MAX")) {
item = new MAX();
data = data.substring(3);
} else if (data.startsWith("min") || data.startsWith("MIN")) {
item = new MIN();
data = data.substring(3);
} else if (data.startsWith("sum") || data.startsWith("SUM")) {
item = new SUM();
data = data.substring(3);
} else if (data.startsWith("count") || data.startsWith("COUNT")) {
item = new COUNT();
data = data.substring(5);
} else {
item = new FIELD(); //simple field
isProjection = true;
// throw new JDOUserException("Query result type not recognised: " + data);
}
String fieldName;
if (item instanceof FIELD) {
//field only
int i = data.indexOf(',');
if (i >= 0) {
fieldName = data.substring(0, i).trim();
data = data.substring(i).trim();
} else {
fieldName = data.trim();
data = "";
}
} else {
data = data.trim();
if (data.charAt(0)!='(') {// {startsWith("(")) {
throw DBLogger.newUser(
"Query result type corrupted, '(' expected at pos 0: " + data);
}
data = data.substring(1);
int i = data.indexOf(')');
if (i < 0) {
throw DBLogger.newUser("Query result type corrupted, ')' not found: " + data);
}
fieldName = data.substring(0, i).trim();
data = data.substring(i).trim();
//remove ')'
data = data.substring(1).trim();
}
items.add(item);
ZooFieldDef def = candClsDef.getAllFieldsAsMap().get(fieldName);
if (def == null) {
throw DBLogger.newUser("Invalid fieldname in result definition: " + fieldName);
}
item.setField(def, resultClass);//getField(candCls, candClsDef, fieldName));
if (!data.isEmpty() && data.charAt(0) == ',') {
data = data.substring(1).trim();
if (data.isEmpty()) {
throw DBLogger.newUser("Trailing comma in result definition not allowed.");
}
}
}
//some verification
for (Item i: items) {
if (!(i instanceof FIELD) && isProjection) {
throw DBLogger.newUser("Mixing of prejection and aggregation is not allowed.");
}
}
}
ArrayList<Object> processResultProjection(Iterator<Object> in, boolean unique) {
//projections
ArrayList<Object> r = new ArrayList<Object>();
if (items.size() == 1) {
Item it = items.get(0);
while (in.hasNext()) {
it.add(in.next());
r.add( it.result() );
}
} else {
while (in.hasNext()) {
Object o = in.next();
Object[] oa = new Object[items.size()];
r.add(oa);
int i = 0;
for (Item it: items) {
it.add(o);
oa[i++] = it.result();
}
}
}
if (unique && r.size() > 1) {
throw DBLogger.newUser("Non-unique result encountered.");
}
return r;
}
Object processResultAggregation(Iterator<Object> in) {
//aggregations
while (in.hasNext()) {
Object o = in.next();
//TODO do this only if UNIQUE is set??? --> check jdo-spec.
for (Item i: items) {
i.add(o);
}
}
//prepare returning results
if (items.size() == 1) {
return items.get(0).result();
} else {
Object[] oa = new Object[items.size()];
for (int i = 0; i < items.size(); i++) {
oa[i] = items.get(i).result();
}
return oa;
}
}
boolean isProjection() {
return isProjection;
}
}