All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zoodb.jdo.QueryResultProcessor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2013 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 .
 * 
 * See the README and COPYING files for further information. 
 */
package org.zoodb.jdo;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;

import javax.jdo.JDOUserException;

import org.zoodb.jdo.internal.SerializerTools.PRIMITIVE;
import org.zoodb.jdo.internal.ZooClassDef;
import org.zoodb.jdo.internal.ZooFieldDef;

/**
 * Processes query results.
 * 
 * See Jdo 2.2 spec 14.6.9.
 * 
 * @author Tilmann Zaeschke
 *
 */
class QueryResultProcessor {

	private final ArrayList items = new ArrayList();
	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 new JDOUserException(
							"Query result type corrupted, '(' expected at pos 0: " + data);
				}
				data = data.substring(1);
				
				int i = data.indexOf(')');
				if (i < 0) {
					throw new JDOUserException(
							"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 new JDOUserException("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 new JDOUserException("Trailing comma in result definition not allowed.");
				}
			}
		}		
		
		//some verification
		for (Item i: items) {
			if (!(i instanceof FIELD) && isProjection) {
				throw new JDOUserException("Mixing of prejection and aggregation is not allowed.");
			}
		}
	}
	
	Object processResult(Collection in, boolean unique) {
		if (unique && isProjection && in.size() > 1) {
			throw new JDOUserException("Non-unique result encountered.");
		}
		
		//calculate results
		Object ret;
		if (isProjection) {
			//projections
			ArrayList r = new ArrayList();
			if (items.size() == 1) {
				Item it = items.get(0);
				for (Object o: in) {
					it.add(o);
					r.add( it.result() );
				}
			} else {
				for (Object o: in) {
					Object[] oa = new Object[items.size()];
					r.add(oa);
					int i = 0;
					for (Item it: items) {
						it.add(o);
						oa[i++] = it.result();
					}
				}
			}
			ret = r;
		} else {
			//aggregations
			for (Object o: in) {
				//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) {
				ret = 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(); 
				}
				ret = oa;
			}
		}
		
		return ret;
	}
}