/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package reportgen.prototype.queryresults;
import reportgen.prototype.context.ContextMode;
import reportgen.prototype.context.group.ContextGroup;
import reportgen.utils.ReportException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jdom.Element;
import reportgen.prototype.context.NoNeedAtom;
import reportgen.prototype.context.Context;
import reportgen.prototype.context.ContextGeneric;
import reportgen.math.ContextFilter;
import reportgen.math.MathExpression;
import reportgen.math.MathExpressionOperand;
import reportgen.math.MathFactory;
import reportgen.math.constants.bool.MathExpressionConstValueBoolean;
import reportgen.math.constants.dateval.MathExpressionConstValueDate;
import reportgen.math.constants.doubleval.MathExpressionConstValueDouble;
import reportgen.math.constants.longval.MathExpressionConstValueLong;
import reportgen.math.constants.stringval.MathExpressionConstValueString;
import reportgen.math.agregate.agregate.AggregateFunction;
import reportgen.utils.SupportXMLRoot;
/**
* Объект - строка результатов.
* Формируется в парсере, затем передается в конструктор результатов.
* Результаты соответствуют результатам выборки и расположены последовательно,
* в том порядке, как шли в списке результатов выборки.
* Поскольку последовательность результатов выборки и результатов отчета одинаковы,
* благодаря этому результаты из строки их можно запрашивать по индексу
* не рассматривая индекс как положение результата выборки, но и как положение
* результата отчета
* index ResultRow CoreColumnResult ReportColumnResult
* 0 [value] at index 0 at index 0
* 1 [value] at index 1 at index 1
* ...
* N [value] at index N at index N
*
* @author axe
*/
public class ResultsRow implements Serializable, Cloneable, SupportXMLRoot {
public static final long serialVersionUID = 1;
public static final String ROWTAG = "results";
private Map<Integer, Integer> normalizeScaler;
private Map<Integer, Set> distinct;
private Integer groupCode;
private Object[] values;
private final static Context context = new ContextGeneric(new NoNeedAtom()) {
@Override
public List<ContextGroup> getAvailiableGroups() {
List<ContextGroup> avail = new ArrayList<ContextGroup>();
avail.add(MathExpressionConstValueBoolean.GROUP);
avail.add(MathExpressionConstValueDate.GROUP);
avail.add(MathExpressionConstValueDouble.GROUP);
avail.add(MathExpressionConstValueLong.GROUP);
avail.add(MathExpressionConstValueString.GROUP);
return avail;
}
@Override
public ContextMode getContextMode() {
return ContextMode.INPUT;
}
};
public ResultsRow(int cols) {
values = new Object[cols];
}
public ResultsRow(Element element) throws ReportException {
List children = element.getChildren();
values = new Object[children.size()];
for(int i = 0; i<children.size(); i++) {
Element iElement = (Element) children.get(i);
MathExpressionOperand express = (MathExpressionOperand)
MathFactory.fromXml(iElement, context, new ContextFilter() {
@Override
public Context getChildContext(ContextGroup group) {
return context;
}
});
values[i] = express.getValue(null);
}
}
@Override
public Element toXML() {
Element root = new Element(ROWTAG);
for(Object value: values) {
MathExpression val = null;
if(value.getClass().equals(Long.class)) {
val = new MathExpressionConstValueLong((Long)value, context);
} else if(value.getClass().equals(Double.class)) {
val = new MathExpressionConstValueDouble((Double)value, context);
} else if(value.getClass().equals(Date.class)) {
val = new MathExpressionConstValueDate((Date)value, context);
} else if(value.getClass().equals(String.class)) {
val = new MathExpressionConstValueString((String)value, context);
} else if(value.getClass().equals(Boolean.class)) {
val = new MathExpressionConstValueBoolean((Boolean)value, context);
} else {
throw new RuntimeException("Сохранение значения '" + value.toString() +
"' как результата подзапроса не поддерживается");
}
root.addContent(val.toXML());
}
return root;
}
public int getWidth() {
return values.length;
}
public void setValue(int index, Object value) {
values[index] = value;
}
public Object getValue(int index) {
return values[index];
}
@Override
public ResultsRow clone() {
try {
ResultsRow row = (ResultsRow) super.clone();
row.values = values.clone();
return row;
} catch (CloneNotSupportedException ex) {
//do nothing, never happened
}
return null;
}
/**
* Проверка, может ли строка группироваться с другой
* @param other
* @return
*/
private boolean canGroupWith(final ResultsRow other, Set<Integer> groupIndicies) {
for(Integer index : groupIndicies) {
Object value1 = values[index];
Object value2 = other.values[index];
if(value1 != value2) {
if(value1 == null
|| value2 == null
|| !value1.equals(value2) ) {
return false;
}
}
}
return true;
}
/**
* Возвращает хеш-код группы
* @return
*/
private int getGroupCode(Set<Integer> groupIndicies) {
if(groupCode == null) {
int hash = 0;
for(Integer index : groupIndicies) {
Object value = values[index];
if(value != null) {
hash += 100 * value.hashCode();
}
}
groupCode = hash;
}
return groupCode;
}
public void initGroup(List<AggregateFunction> cols) throws ReportException {
initGroup(cols, false);
}
public void initSummRow(List<AggregateFunction> cols) throws ReportException {
initGroup(cols, true);
}
/**
* Инициализирует первую строку в группе, изменяя для некоторых
* столбцов тип значения, соответственно функции столбца
*/
private void initGroup(List<AggregateFunction> cols, boolean clearAsis) throws ReportException {
//init normalizer
normalizeScaler = new HashMap<Integer, Integer>();
//init columns
for(int i=0; i<cols.size(); i ++) {
AggregateFunction col = cols.get(i);
if(col.equals(AggregateFunction.COUNT)) {
if(values[i] != null) {
values[i] = new Long(1);
} else {
values[i] = new Long(0);
}
} else if(col.equals(AggregateFunction.COUNT_DISTINCT)) {
if(distinct == null) {
distinct = new HashMap<Integer, Set>();
}
Set set = new HashSet();
if(values[i] != null) {
set.add(values[i]);
values[i] = new Long(1);
} else {
values[i] = new Long(0);
}
distinct.put(i, set);
} else if(col.equals(AggregateFunction.AVG)) {
Object value = values[i]; //always be Long or Double
if(value instanceof Long) {
values[i] = new Double(((Long)value).doubleValue());
} else if (value instanceof Double) {
//fit, do nothing
} else {
throw new ReportException("Невозможно инициализировать группу при рассчете среднего значения для класса "
+ value.getClass().getSimpleName());
}
normalizeScaler.put(i, 1);
} else if(clearAsis
&& col.equals(AggregateFunction.ASIS)) {
values[i] = new String("-");
}
}
}
/**
* Добавляет строку в слияние (группу)
* @param newRow строка которую нужно слить
* @param groupIndicies индексы столбцов по которым осуществляется группировка
* null, если априори известно что строки нужно слить
* @param cols массив функций слияния столбцов
* @return
* @throws reportgen.ren.exception.ReportException
*/
public boolean merge(ResultsRow newRow, Set<Integer> groupIndicies,
List<AggregateFunction> cols) throws ReportException {
if(groupIndicies != null
&& (getGroupCode(groupIndicies) != newRow.getGroupCode(groupIndicies)
|| !canGroupWith(newRow, groupIndicies))) {
return false;
}
for(int i=0; i< cols.size(); i++) {
AggregateFunction col = cols.get(i);
Object value = newRow.values[i];
//null values not take apart in group merging
if(value == null) {
continue;
}
if(col.equals(AggregateFunction.SUM)) {
values[i] = sum(values[i], value);
} else if(col.equals(AggregateFunction.AVG)) {
Double dval = null;
if(value instanceof Long) {
dval = new Double(((Long) value).doubleValue());
} else if(value instanceof Double) {
dval = (Double) value;
} else {
throw new ReportException("Невозможно рассчитать среднее значение для класса "
+ value.getClass().getSimpleName());
}
values[i] = (Double) sum(values[i], dval);
//update scaler
Integer scaler = normalizeScaler.get(i);
normalizeScaler.put(i, scaler+1);
} else if(col.equals(AggregateFunction.COUNT)) {
values[i] = (Long) values[i] + 1;
} else if(col.equals(AggregateFunction.COUNT_DISTINCT)) {
Set set = distinct.get(i);
if(!set.contains(value)) {
set.add(value);
values[i] = (Long) values[i] + 1;
}
} else if(col.equals(AggregateFunction.MAX)) {
if(compare(values[i], value) < 0) {
values[i] = value;
}
} else if(col.equals(AggregateFunction.MIN)) {
if(compare(values[i], value) > 0) {
values[i] = value;
}
}
}
return true;
}
private Object sum(Object value1, Object value2) throws ReportException {
if(value1.getClass() != value2.getClass()) {
throw new ReportException("Внутренняя ошибка, типы данных в разных строках результатов не совпадают");
}
Class cls = value1.getClass();
if(cls.equals(Double.class)) {
Double val = (Double)value1 + (Double)value2;
return val;
} else if(cls.equals(Long.class)) {
Long val = (Long)value1 + (Long)value2;
return val;
} else {
throw new ReportException("Внутренняя ошибка, неизвестный тип данных результах отчетов: " + cls.getSimpleName());
}
}
/**
* Сравнивает два объекта
* @param value1
* @param value2
* @return
* @throws beans.reportgen.ren.ReportException
*/
private int compare(Object value1, Object value2) throws ReportException {
if(value1.getClass() != value2.getClass()) {
throw new ReportException("Внутренняя ошибка, типы данных в разных строках результатов не совпадают");
}
Class cls = value1.getClass();
if(cls.equals(Double.class)) {
return ((Double)value1).compareTo((Double)value2);
} else if(cls.equals(Long.class)) {
return ((Long)value1).compareTo((Long)value2);
} else {
throw new ReportException("Внутренняя ошибка, невозможно сравнить данные результов отчета: " + cls.getSimpleName());
}
}
/**
* Нормализует значение сгруппированных значений строки
*/
public void normalize(List<AggregateFunction> cols) throws ReportException {
for(Integer col: normalizeScaler.keySet()) {
values[col] = normalize(values[col], normalizeScaler.get(col));
}
}
/**
* Нормализует значение сгруппировонной колонки
* @param value
* @return
* @throws beans.reportgen.ren.ReportException
*/
private Object normalize(Object value, int count) throws ReportException {
Class cls = value.getClass();
if(cls.equals(Double.class)) {
Double val = ((Double)value)/count;
return val;
} else if(cls.equals(Long.class)) {
Long val = ((Long)value)/count;
return val;
} else {
throw new ReportException("Внутренняя ошибка, неизвестный тип данных при нормализации результатов отчета: " + cls.getSimpleName());
}
}
}