0001: package com.calipso.reportgenerator.reportcalculator;
0002:
0003: import com.calipso.reportgenerator.common.InfoException;
0004:
0005: import java.util.*;
0006: import java.io.Serializable;
0007: import java.io.ObjectOutputStream;
0008: import java.io.IOException;
0009: import java.io.ObjectInputStream;
0010:
0011: import com.calipso.reportgenerator.common.ReportMetricSpec;
0012: import com.calipso.reportgenerator.common.LanguageTraslator;
0013: import com.calipso.reportgenerator.reportdefinitions.types.CalculationType;
0014:
0015: /**
0016: * La clase Cube representa la información de un reporte.
0017: * Podríamos verlo como el resultado de la aplicación de una
0018: * query a un Pivot. La estructura del Cube es un árbol.
0019: * En la raíz del mismo hay un array que contiene una serie de
0020: * dimensiones seguida de las métricas.
0021: * Mientras en las métricas está acumulado el total de cada una, en las
0022: * dimensiones y diccionarios. Cada uno de estos diccionarios contiene, como
0023: * clave, los distintos valores para la dimension correspondiente (la posición
0024: * en el array). Como valor, los diccionarios contienen un nuevo array similar
0025: * al de la raíz.
0026: * El resultado es una estructura arbórea que alterna nodos tipo array con
0027: * diccionarios.
0028: * Por cuestiones de performance se ha utilizado la clase SharedFloat en lugar
0029: * de los Float tradicionales
0030: * @see com.calipso.reportgenerator.reportcalculator.CubeQuery
0031: * @see Pivot
0032: * @see SharedFloat
0033: */
0034:
0035: public class Cube implements Serializable, PivotClient {
0036: private CubeDefinition definition;
0037: private CubeQuery query;
0038: private Object[] root;
0039: private int arraySize;
0040: private int metricsSize;
0041: private LinkedList dimensionsCombinations;
0042: private Set[] dimensionValues;
0043: private MetricCalculationStrategy[] metricStrategies;
0044: private MetricCalculationStrategy[] groupFooterStrategies;
0045: private int dimensionsSize;
0046: private int[] queryMetrics;
0047: private static final int INVALID_DIMENSION = 999;
0048: private Object[] metrics;
0049: private Map excludedNodes = new HashMap();
0050: private static final String others = LanguageTraslator.traslate(
0051: "404").intern();//SharedString.newFrom(LanguageTraslator.traslate("404"));
0052: private Pivot pivot;
0053: private int fullDimensionsSize;
0054:
0055: /**
0056: * Retorna un entero que determina la cantidad de dimensiones
0057: * @return
0058: */
0059: public int getDimensionsSize() {
0060: return dimensionsSize;
0061: }
0062:
0063: /**
0064: * Inicializa la cantidad de dimensiones y métricas del Cube
0065: * a partir de la cantidad de dimensiones y métricas de la definicion.
0066: * Inicializa la variable arraySize de acuerdo a la suma de metricas
0067: * y dimensiones, es decir, el total de columnas.
0068: * Tambien inicializa las formas de calculo de metricas y totales. Inicializa las
0069: * metricas auxiliares para el cálculo de promedios.
0070: */
0071: private void initialize() {
0072: metrics = definition.getMetrics();
0073: initAverageCountMetrics();
0074: metricsSize = getMetrics().length;
0075: dimensionsSize = query.getDimensions().length;//definition.getDimensions().length;
0076: fullDimensionsSize = definition.getDimensions().length;
0077: setArraySize(dimensionsSize + metricsSize);
0078: initMetricStrategies();
0079: initGroupFooterStrategies();
0080: excludedNodes = new HashMap();
0081: }
0082:
0083: /**
0084: * Inicializa las metricas adicionales que se usaran para el calculo de los promedios. Por cada metrica crea
0085: * una adicional que contara las ocurrencias.
0086: */
0087: private void initAverageCountMetrics() {
0088: if (this .hasAverageMetrics()) {
0089: ReportMetricSpec countMetric;
0090: Object[] resultMetrics = new Object[(metrics.length * 2)];
0091: for (int i = 0; i < metrics.length; i++) {
0092: resultMetrics[i] = metrics[i];
0093: countMetric = new ReportMetricSpec("Count" + i);
0094: countMetric.setAggregateType(CalculationType.COUNT);
0095: countMetric.setGroupFooterType(CalculationType.COUNT);
0096: resultMetrics[metrics.length + i] = countMetric;
0097: }
0098: metrics = resultMetrics;
0099: }
0100: }
0101:
0102: /**
0103: * Retorna verdadero si existen metricas que utilizan average.
0104: * @return
0105: */
0106: private boolean hasAverageMetrics() {
0107: return getAverageMetricsIndexes().length > 0;
0108: }
0109:
0110: /**
0111: * Busca las metricas que utilizan average como tipo de calculo. Arma un array con los indices de dichas metricas.
0112: * @return un array con los indices de las metricas que utilizan average
0113: */
0114: private int[] getAverageMetricsIndexes() {
0115: Object[] metrics = definition.getMetrics();
0116: LinkedList indexes = new LinkedList();
0117: for (int i = 0; i < metrics.length; i++) {
0118: Object metric = metrics[i];
0119: if (metric instanceof ReportMetricSpec) {
0120: ReportMetricSpec metricSpec = (ReportMetricSpec) metric;
0121: if ((metricSpec.getAggregateType().getType() == CalculationType.AVERAGE_TYPE || metricSpec
0122: .getGroupFooterType().getType() == CalculationType.AVERAGE_TYPE)
0123: && isMetricInQuery(i)) {
0124: indexes.add(new Integer(i));
0125: }
0126: }
0127: }
0128: return toIntArray(indexes);
0129: }
0130:
0131: /**
0132: * Retorna true si la metrica pasada por parametro esta en la query
0133: * @param i indice de la metrica que se busca en la query
0134: * @return true si la metrica esta en la query
0135: */
0136: private boolean isMetricInQuery(int i) {
0137: for (int j = 0; j < queryMetrics.length; j++) {
0138: if (i + definition.getDimensions().length == queryMetrics[j]) {
0139: return true;
0140: }
0141: }
0142: return false;
0143: }
0144:
0145: /**
0146: * Inicializa el array con las estrategias de calculo de los totales tomadas de los ReportMetricSpec de cada metrica.
0147: */
0148: private void initGroupFooterStrategies() {
0149: Object[] metrics = getMetrics();
0150: groupFooterStrategies = new MetricCalculationStrategy[metrics.length];
0151: for (int i = 0; i < metrics.length; i++) {
0152: Object metric = metrics[i];
0153: if (metric instanceof ReportMetricSpec) {
0154: groupFooterStrategies[i] = MetricCalculationStrategy
0155: .getFooterStrategyFor((ReportMetricSpec) metric);
0156: } else {
0157: groupFooterStrategies[i] = new SumStrategy();
0158: }
0159: }
0160: }
0161:
0162: /**
0163: * Retorna las metricas del cubo. Normalmente seran las metricas que esten en el definition, pero si
0164: * existen metricas que calculen AVERAGE las metricas estaran duplicadas (dicha replica contara
0165: * realizara un COUNT para porder tener la cantidad sobre la cual dividir).
0166: * @return
0167: */
0168: private Object[] getMetrics() {
0169: return metrics;
0170: }
0171:
0172: /**
0173: * Inicializa las estrategias de calculo para cada metrica.
0174: */
0175: private void initMetricStrategies() {
0176: Object[] metrics = getMetrics();
0177: metricStrategies = new MetricCalculationStrategy[metrics.length];
0178: for (int i = 0; i < metrics.length; i++) {
0179: Object metric = metrics[i];
0180: if (metric instanceof ReportMetricSpec) {
0181: metricStrategies[i] = MetricCalculationStrategy
0182: .getMetricStrategyFor((ReportMetricSpec) metric);
0183: } else {
0184: metricStrategies[i] = new SumStrategy();
0185: }
0186: }
0187: }
0188:
0189: /**
0190: * Retorna la definicion del Cube
0191: * @return
0192: */
0193: public CubeDefinition getDefinition() {
0194: return definition;
0195: }
0196:
0197: /**
0198: * Asigna al Cube la definición de Cube correspondiente
0199: * @param definition
0200: */
0201: public void setDefinition(CubeDefinition definition) {
0202: this .definition = definition;
0203: }
0204:
0205: /**
0206: * Retorna la Query del Cube
0207: * @return
0208: */
0209: public CubeQuery getQuery() {
0210: return query;
0211: }
0212:
0213: /**
0214: * Asigna la nueva query a ejecutar
0215: * @param query
0216: * @throws InfoException Si se produjo un error en el cálculo del cubo.
0217: */
0218: public void setQuery(CubeQuery query) throws InfoException {
0219: try {
0220: this .query = query;
0221: queryMetrics = getQuery().getMetrics();
0222: setDimensionsCombinations();
0223: } catch (Exception e) {
0224: throw new InfoException(
0225: com.calipso.reportgenerator.common.LanguageTraslator
0226: .traslate("102"), e);
0227: }
0228: }
0229:
0230: /**
0231: * Retorna un array que representa la raíz del cube
0232: * y que contiene una serie de dimensiones seguidas de las métricas
0233: * @return
0234: */
0235: public Object[] getRoot() {
0236: return root;
0237: }
0238:
0239: /**
0240: * Asigna un array que representa la raíz del cube y que contiene
0241: * una serie de dimensiones seguidas de las métricas
0242: * @param root
0243: */
0244: public void setRoot(Object[] root) {
0245: this .root = root;
0246: }
0247:
0248: /**
0249: * Retorna arraySize que es un entero que
0250: * representa la suma de dimensiones y métricas, es decir el total de columnas del Cube.
0251: * @return
0252: */
0253: public int getArraySize() {
0254: return arraySize;
0255: }
0256:
0257: /**
0258: * Inicializa arraySize a partir de un entero que
0259: * representa la suma de dimensiones y métricas, es decir el total de columnas del Cube.
0260: * @param arraySize
0261: */
0262: public void setArraySize(int arraySize) {
0263: this .arraySize = arraySize;
0264: }
0265:
0266: /**
0267: * Retorna la cantidad total de métricas del Cube.
0268: * @return
0269: */
0270: public int getMetricsSize() {
0271: return metricsSize;
0272: }
0273:
0274: /**
0275: * Crea todas las combinaciones de dimensiones necesarias para cualquier tipo de totales
0276: * @throws InfoException
0277: */
0278: private void setDimensionsCombinations() throws InfoException {
0279: dimensionsCombinations = new LinkedList();
0280: int[] cols = query.getColumns();
0281: int[] rows = query.getRows();
0282: for (int i = 1; i <= rows.length; i++) {
0283: int[] list = new int[i];
0284: for (int j = 0; j < i; j++) {
0285: list[j] = j;
0286: }
0287: dimensionsCombinations.add(list);
0288: for (int z = 1; z <= cols.length; z++) {
0289: int[] withCol = new int[i + z];
0290: System.arraycopy(list, 0, withCol, 0, list.length);
0291: for (int j = 0; j < z; j++) {
0292: withCol[j + i] = rows.length + j;
0293: }
0294: dimensionsCombinations.add(withCol);
0295: }
0296: }
0297: if (rows.length != 0) {
0298: for (int i = 1; i <= cols.length; i++) {
0299: int[] list = new int[i];
0300: for (int j = 0; j < i; j++) {
0301: list[j] = rows.length + j;
0302: }
0303: dimensionsCombinations.add(list);
0304: }
0305: }
0306: /*int[] dimensions;
0307: int factor;
0308: int[] list;
0309: int queryDimensionsSize;
0310: int combination;
0311: String bits;
0312: int bitsLenght;
0313: int pos;
0314: try{
0315: dimensions = getIntegerArray(getQuery().getDimensions().length);
0316: queryDimensionsSize = dimensions.length;
0317: dimensionsCombinations = new LinkedList();
0318: for (combination = 1; combination <= (Math.pow(2, queryDimensionsSize) - 1); combination++) {
0319: bits = Integer.toBinaryString(combination);
0320: bitsLenght = bits.length();
0321: list = new int[bitsLenght];
0322: for (int index = 0; index < bitsLenght; index++) {
0323: pos = bitsLenght - 1 - index;
0324: factor = Character.digit(bits.charAt(pos), 10);
0325: if (factor == 1) {
0326: list[index] = factor * dimensions[index];
0327: }
0328: else {
0329: list[index] = INVALID_DIMENSION;
0330: }
0331: }
0332: //if(list[0]==0){
0333: dimensionsCombinations.add(list);
0334: //}
0335: }
0336: }
0337: catch (Exception e){
0338: throw new InfoException(com.calipso.reportgenerator.common.LanguageTraslator.traslate("103"),e);
0339: } */
0340: }
0341:
0342: public int[] getIntegerArray(int base, int length) {
0343: int[] result = new int[length];
0344: for (int j = 0, i = base; j < result.length; i++, j++) {
0345: result[j] = i;
0346: }
0347: return result;
0348: }
0349:
0350: /**
0351: * Reinicializa el Cube. Como efecto se pierden todos los datos actuales.
0352: */
0353: public void reset() {
0354: initialize();
0355: root = null;
0356: System.gc();
0357: root = newArray();
0358: }
0359:
0360: /**
0361: * Crea un array para un nodo con el tamaño correspondiente según la cantidad de dimensiones y métricas
0362: * inicializando los valores de las métricas con 0
0363: *
0364: * @return
0365: */
0366: private Object[] newArray() {
0367: Object[] result;
0368: int index;
0369: result = new Object[arraySize];
0370: for (index = dimensionsSize; index < arraySize; index++) {
0371: result[index] = SharedFloat.newFrom(Float.NaN);
0372: }
0373:
0374: return result;
0375: }
0376:
0377: /**
0378: * Agrega row si pasa por los filtros
0379: * @param row
0380: */
0381: public void fillWith(Object[] row) {
0382: if (query.matches(row)) {
0383: fillDimensionValues(row);
0384: if (query.valuesEnabled(row)) {
0385: row = shrinkRow(row);
0386: basicFillWith(row);
0387: }
0388: } else if (query.isGroupExcludedValues()) {
0389: if (query.otherFilterMatches()) {
0390: row = shrinkRow(row);
0391: fillExcludedValueNode(row);
0392: }
0393: }
0394: }
0395:
0396: /**
0397: * Selecciona de row solo las dimensiones necesarias y las ordena
0398: * @param row
0399: * @return
0400: */
0401: private Object[] shrinkRow(Object[] row) {
0402: int[] dimensions = query.getDimensions();
0403: int[] metrics = query.getMetrics();
0404: Object[] result = new Object[dimensions.length + metrics.length];
0405: int i = 0;
0406: for (; i < dimensions.length; i++) {
0407: result[i] = row[dimensions[i]];
0408: }
0409: for (int j = 0; j < metrics.length; i++, j++) {
0410: result[i] = row[metrics[j]];
0411: }
0412: return result;
0413: }
0414:
0415: private void fillExcludedValueNode(Object[] aRow) {
0416: Object[] measures;
0417: Iterator iterator;
0418: //EnumerationCubeFilter filter = query.getExcludeGroupFilter();
0419: measures = valuesOfFrom(queryMetrics, aRow);
0420: iterator = dimensionsCombinations.iterator();
0421: while (iterator.hasNext()) {
0422: /* Agarro dimension por dimension. Me fijo si el valor de esa dimension en
0423: este row esta o va a otros.
0424: Si esta, busco su nodo y la agrego. Pero en los subnodos me tengo que fijar
0425: si los valores de esas dimensiones estan o van en otros.
0426: Si va a otros, agrego al nodo de otros de esta dimension, pero nuevamente
0427: en los subnodos debo ver si esta el valor o va a otros.
0428: */
0429: excludedAdd((int[]) iterator.next(), aRow, measures);
0430: }
0431: addTotal(measures, root, aRow);
0432: }
0433:
0434: private void excludedAdd(int[] dimensions, Object[] aRow,
0435: Object[] measures) {
0436: Object[] node;
0437: node = getExcludedNode(dimensions, aRow);
0438: if (isLastLevel(dimensions)) {
0439: addTo(measures, node, aRow);
0440: } else {
0441: addTotal(measures, node, aRow);
0442: }
0443: }
0444:
0445: private Object[] getExcludedNode(int[] dimensions, Object[] aRow) {
0446: Object[] node = root;
0447: for (int i = 0; i < dimensions.length; i++) {
0448: int dimension = dimensions[i];
0449: if (dimension != INVALID_DIMENSION) {
0450: if (node[dimension] == null) {
0451: node[dimension] = new HashMap();
0452: }
0453: Object value;
0454: //Verifica si el filtro que excluye valores tiene la dimension, y luego que el valor este excluido (no este en el filtro).
0455: if (query.getExcludeGroupFilter().hasDimension(
0456: dimension)
0457: && !query.getExcludeGroupFilter()
0458: .containsValue(dimension,
0459: aRow[dimension])) {
0460: value = others;
0461: } else {
0462: value = aRow[dimension];
0463: }
0464: if (!((HashMap) node[dimension]).containsKey(value)) {
0465: ((HashMap) node[dimension]).put(value, newArray());
0466: }
0467: node = (Object[]) ((HashMap) node[dimension])
0468: .get(value);
0469: }
0470: }
0471: /*Iterator it = excludedNodes.entrySet().iterator();
0472: Object[] excludedNode = null;
0473: while(it.hasNext() && excludedNode==null){
0474: Map.Entry entry = (Map.Entry)it.next();
0475: if(Arrays.equals((int [])entry.getKey(), dimensions)){
0476: excludedNode = (Object[])entry.getValue();
0477: }
0478: }
0479: if(excludedNode==null){
0480: excludedNode = newExcludedNode(dimensions);
0481: excludedNodes.put(dimensions, excludedNode);
0482: //addExcludedNode(excludedNode, dimensions);
0483: }
0484: return excludedNode;*/
0485: return node;
0486: }
0487:
0488: private void addExcludedNode(Object[] excludedNode, int[] dimensions) {
0489: Object[] node = root;
0490: for (int i = 0; i < dimensions.length - 1; i++) {
0491: int dimension = dimensions[i];
0492: if (dimension != INVALID_DIMENSION) {
0493: HashMap map = (HashMap) node[dimension];
0494: if (map.containsKey(others)) {
0495: node = (Object[]) map.get(others);
0496: }
0497: }
0498: }
0499: Object child = node[dimensions[dimensions.length - 1]];
0500: if (child == null) {
0501: child = new HashMap();
0502: node[dimensions[dimensions.length - 1]] = child;
0503: }
0504: ((Map) child).put(others, excludedNode);
0505: }
0506:
0507: private Object[] newExcludedNode(int[] dimensions) {
0508: Object[] node = newArray();
0509: for (int i = 0; i < dimensions.length; i++) {
0510: int dimension = dimensions[i];
0511: if (dimension != INVALID_DIMENSION) {
0512: HashMap dict = new HashMap();
0513: Object[] subNode = getSubNode(dimensions, i + 1);
0514: dict.put(others, subNode);
0515: node[dimension] = dict;
0516: }
0517: }
0518: return node;
0519: }
0520:
0521: private Object[] getSubNode(int[] dimensions, int index) {
0522: Object[] node = newArray();
0523: for (int j = index; j < dimensions.length; j++) {
0524: int dimension = dimensions[j];
0525: if (dimension != INVALID_DIMENSION) {
0526: HashMap dict = new HashMap();
0527: Object[] subNode = getSubNode(dimensions, j + 1);
0528: dict.put(others, subNode);
0529: node[dimension] = dict;
0530: }
0531: }
0532: return node;
0533: }
0534:
0535: /**
0536: * Resuelve la inclusión de un nuevo row actualizando los totales correspondientes
0537: * @param aRow
0538: */
0539: protected void basicFillWith(Object[] aRow) {
0540: Object[] measures;
0541: Iterator iterator;
0542: measures = valuesOfFrom(queryMetrics, aRow);
0543: iterator = dimensionsCombinations.iterator();
0544: while (iterator.hasNext()) {
0545: atFromAdd((int[]) iterator.next(), aRow, measures);
0546: }
0547: addTotal(measures, root, aRow);
0548: }
0549:
0550: private void fillDimensionValues(Object[] row) {
0551: int[] dims;
0552: dims = query.getRows();
0553: fillDimensionValuesFromArray(dims, row);
0554: dims = query.getColumns();
0555: fillDimensionValuesFromArray(dims, row);
0556: dims = query.getPages();
0557: fillDimensionValuesFromArray(dims, row);
0558: }
0559:
0560: private void fillDimensionValuesFromArray(int[] dimensions,
0561: Object[] row) {
0562: for (int i = 0; i < dimensions.length; i++) {
0563: int dim = dimensions[i];
0564: if (row[dim] != null) {
0565: getDimensionValues()[dim].add(row[dim]);
0566: }
0567: }
0568: }
0569:
0570: /**
0571: * Resuelve la operación de sumarización
0572: * @param measures
0573: * @param node
0574: */
0575: private void addTo(Object[] measures, Object[] node, Object[] aRow) {
0576: int index;
0577: for (index = 0; index < metricsSize; index++) {
0578: node[index + dimensionsSize] = (metricStrategies[index])
0579: .operate(node, index + dimensionsSize,
0580: measures[index], aRow);
0581: //((SharedFloat) node[index + dimensionsSize]).add(measures[index]);
0582: }
0583: }
0584:
0585: /**
0586: * Suma los measures en las coordenadas dadas por dimensions y el contenido de aRow
0587: * @param dimensions
0588: * @param aRow
0589: * @param measures
0590: */
0591: private void atFromAdd(int[] dimensions, Object[] aRow,
0592: Object[] measures) {
0593: Object[] node;
0594: node = atFrom(dimensions, aRow);
0595: if (node != null) {
0596: if (isLastLevel(dimensions)) {
0597: addTo(measures, node, aRow);
0598: } else {
0599: addTotal(measures, node, aRow);
0600: }
0601: }
0602: }
0603:
0604: /**
0605: * Agrega valores totales al cubo, realizando el calculo segun la estrategia de total que tenga la metrica.
0606: * @param measures los valores a agregar
0607: * @param node el nodo donde se opera
0608: */
0609: private void addTotal(Object[] measures, Object[] node,
0610: Object[] aRow) {
0611: for (int index = 0; index < metricsSize; index++) {
0612: node[index + dimensionsSize] = (groupFooterStrategies[index])
0613: .operate(node, index + dimensionsSize,
0614: measures[index], aRow);
0615: }
0616: }
0617:
0618: /**
0619: * Comprueba si las dimensiones especificadas darian un nodo final (de ultimo nivel) o si es un nodo que agrupa.
0620: * @param dimensions
0621: * @return
0622: */
0623: private boolean isLastLevel(int[] dimensions) {
0624: if (dimensions.length < getQuery().getDimensions().length) {
0625: return false;
0626: }
0627: for (int i = 0; i < dimensions.length; i++) {
0628: int dimension = dimensions[i];
0629: if (dimension == INVALID_DIMENSION) {
0630: return false;
0631: }
0632: }
0633: return true;
0634: }
0635:
0636: /**
0637: * Retorna los valores para las coordenadas dadas por dimensions y el contenido de aRow
0638: * Va recorriendo recursivamente los nodos del tipo array hasta agotar las dimensions
0639: * @param dimensions
0640: * @param aRow
0641: * @return
0642: */
0643: private Object[] atFrom(int[] dimensions, Object[] aRow) {
0644: Object[] node;
0645: int index;
0646: int dimension;
0647: int dimensionsLenght;
0648: Object value;
0649:
0650: node = root;
0651: dimensionsLenght = dimensions.length;
0652: for (index = 0; index < dimensionsLenght; index++) {
0653: dimension = dimensions[index];
0654: if (dimension != INVALID_DIMENSION) {
0655: value = aRow[dimension];
0656: if (value != null) {
0657: node = atDimensionIn(value, dimension, node);
0658: } else {
0659: return null;
0660: }
0661: }
0662: }
0663: return node;
0664: }
0665:
0666: /**
0667: * Retorna en un array los valores contenidos en aRow para las métricas dadas por metrics
0668: * @param metrics
0669: * @param aRow
0670: * @return
0671: */
0672: private Object[] valuesOfFrom(int[] metrics, Object[] aRow) {
0673: Object[] array = new Object[metricsSize];
0674: System.arraycopy(aRow, query.getDimensions().length, array, 0,
0675: query.getMetrics().length);
0676: /*array = new Object[metricsSize];
0677: for (int index = 0; index < metrics.length; index++) {
0678: if (aRow[metrics[index]]!=null){
0679: array[index] = (/*(SharedFloat)*//*aRow[metrics[index]]);
0680: }else{
0681: array[index] = null;
0682: }
0683: }*/
0684: return array;
0685: /* float[] array;
0686:
0687: array = new float[metricsSize];
0688: for ( int index = 0; index < metricsSize; index++ )
0689: {
0690: array[index] = ((Float) aRow[ metrics[index] ]).floatValue();
0691: }
0692:
0693: return array;*/
0694: }
0695:
0696: /**
0697: * Retorna el porcentaje, con respecto a la fila, de la métrica metric según las
0698: * coordenadas indicadas por dimensions y values
0699: * @param metric
0700: * @param dimensions
0701: * @param values
0702: * @return
0703: */
0704: public float rowPercentageOf(int metric, int[] dimensions,
0705: Object[] values) {
0706: float total;
0707:
0708: total = rowTotalOf(metric, dimensions, values);
0709: return percentageOf(metric, dimensions, values, total);
0710: }
0711:
0712: /**
0713: * Devuelve el total de una métrica en una fila para las dimensiones que se reciben como parámetro
0714: * @param metric
0715: * @param dimensions
0716: * @param values
0717: * @return
0718: */
0719: private float rowTotalOf(int metric, int[] dimensions,
0720: Object[] values) {
0721: return totalOf(metric, dimensions, values, getQuery().getRows());
0722: }
0723:
0724: /**
0725: * Retorna el porcentaje, con respecto a la columna, de la métrica metric según las
0726: * coordenadas indicadas por dimensions y values
0727: * @param metric
0728: * @param dimensions
0729: * @param values
0730: * @return
0731: */
0732: public float columnPercentageOf(int metric, int[] dimensions,
0733: Object[] values) {
0734: float total;
0735:
0736: total = columnTotalOf(metric, dimensions, values);
0737: return percentageOf(metric, dimensions, values, total);
0738: }
0739:
0740: /**
0741: * Devuelve el total de una columna para una métrica para las dimensiones que se reciben como parámetro
0742: * @param metric
0743: * @param dimensions
0744: * @param values
0745: * @return
0746: */
0747: private float columnTotalOf(int metric, int[] dimensions,
0748: Object[] values) {
0749: return totalOf(metric, dimensions, values, getQuery()
0750: .getColumns());
0751: }
0752:
0753: /**
0754: * Devuelve el total de una métrica para las dimensiones que se reciben como parámetro
0755: * @param metric
0756: * @param dimensions
0757: * @param values
0758: * @param rows
0759: * @return
0760: */
0761: private float totalOf(int metric, int[] dimensions,
0762: Object[] values, int[] rows) {
0763: int dimension;
0764: LinkedList selectedDimensions;
0765: LinkedList selectedValues;
0766: boolean found;
0767: int row;
0768: int dimensionsLenght;
0769: int rowsLenght;
0770:
0771: selectedDimensions = new LinkedList();
0772: selectedValues = new LinkedList();
0773: dimensionsLenght = dimensions.length;
0774: for (int index = 0; index < dimensionsLenght; index++) {
0775: dimension = dimensions[index];
0776: rowsLenght = rows.length;
0777: found = false;
0778: for (row = 0; (row < rowsLenght) && !found; row++) {
0779: found = (rows[row] == dimension);
0780: }
0781: if (found) {
0782: selectedDimensions.add(new Integer(dimension));
0783: selectedValues.add(values[index]);
0784: }
0785: }
0786:
0787: return measureAtDimensionsValues(metric,
0788: toIntArray(selectedDimensions), selectedValues
0789: .toArray());
0790: }
0791:
0792: /**
0793: * Devuelve un array de enteros con los índices seleccionados
0794: * @param selectedIndexes
0795: * @return
0796: */
0797: private int[] toIntArray(LinkedList selectedIndexes) {
0798: int[] result;
0799: int index;
0800: Iterator iterator;
0801: int selectedDimensionsSize;
0802:
0803: result = new int[selectedIndexes.size()];
0804: iterator = selectedIndexes.iterator();
0805: selectedDimensionsSize = selectedIndexes.size();
0806: for (index = 0; index < selectedDimensionsSize; index++) {
0807: result[index] = ((Integer) iterator.next()).intValue();
0808: }
0809: return result;
0810: }
0811:
0812: /**
0813: * Devuelve el porcentaje de una métrica para las dimensiones especificadas
0814: * @param metric
0815: * @param dimensions
0816: * @param values
0817: * @param total
0818: * @return
0819: */
0820: private float percentageOf(int metric, int[] dimensions,
0821: Object[] values, float total) {
0822: float value;
0823:
0824: value = measureAtDimensionsValues(metric, dimensions, values);
0825: if (total == 0) {
0826: return 0;
0827: } else {
0828: return value * 100 / total;
0829: }
0830: }
0831:
0832: /**
0833: * Retorna el valor de la métrica metric según las coordenadas indicadas por
0834: * dimensions y values
0835: * @param metric
0836: * @param dimensions
0837: * @param values
0838: * @return
0839: */
0840: public float measureAtDimensionsValues(int metric,
0841: int[] dimensions, Object[] values) {
0842: int metricIndex;
0843: Object[] measures;
0844:
0845: for (metricIndex = 0; metricIndex < getMetricsSize(); metricIndex++) {
0846: if (getQuery().getMetrics()[metricIndex] == metric) {
0847: break;
0848: }
0849: }
0850: measures = measuresAtDimensionsValues(dimensions, values);
0851:
0852: return ((SharedFloat) measures[metricIndex + dimensionsSize])
0853: .floatValue();
0854: }
0855:
0856: /**
0857: * Retorna los valores de las métricas según las coordenadas indicadas por
0858: * dimensions y values
0859: * @param dimensions
0860: * @param values
0861: * @return
0862: */
0863: public Object[] measuresAtDimensionsValues(int[] dimensions,
0864: Object[] values) {
0865: Object[] node;
0866: int index;
0867: int dimensionsLenght;
0868:
0869: node = root;
0870: dimensionsLenght = dimensions.length;
0871: for (index = 0; index < dimensionsLenght; index++) {
0872: node = atDimensionIn(values[index], dimensions[index], node);
0873: }
0874: return node;
0875: }
0876:
0877: /**
0878: * Retorna el nodo tipo array a partir del node y el valor value para la
0879: * dimensión. Si no existen el nodo tipo diccionario y el tipo
0880: * array, los crea
0881: * @param value
0882: * @param dimension
0883: * @param node
0884: * @return
0885: */
0886: private Object[] atDimensionIn(Object value, int dimension,
0887: Object[] node) {
0888: HashMap dict;
0889: Object[] array;
0890: Object o;
0891:
0892: if ((node[dimension] instanceof HashMap)) {
0893: dict = (HashMap) node[dimension];
0894: } else {
0895: node[dimension] = dict = new HashMap();
0896: }
0897: o = dict.get(value);
0898: if (o == null) {
0899: array = newArray();
0900: dict.put(value, array);
0901: return array;
0902: } else {
0903: return (Object[]) o;
0904: }
0905: }
0906:
0907: /**
0908: * Resolución de la serialización
0909: * @param stream
0910: * @throws IOException
0911: */
0912: public void writeTo(ObjectOutputStream stream) throws IOException {
0913: stream.writeObject(this );
0914: }
0915:
0916: /**
0917: * Resolución de la des-serialización
0918: * @param stream
0919: * @throws IOException
0920: * @throws ClassNotFoundException
0921: */
0922: public void readFrom(ObjectInputStream stream, Pivot pivot)
0923: throws IOException, ClassNotFoundException {
0924: Cube cube;
0925:
0926: pivot = null;
0927: cube = (Cube) stream.readObject();
0928: this .arraySize = cube.arraySize;
0929: this .definition = cube.definition;
0930: this .dimensionsCombinations = cube.dimensionsCombinations;
0931: this .dimensionsSize = cube.dimensionsSize;
0932: this .metricsSize = cube.metricsSize;
0933: this .query = cube.query;
0934: this .queryMetrics = cube.queryMetrics;
0935: this .root = cube.root;
0936: }
0937:
0938: /**
0939: * Incompleto. Es para agregar incrementalmente una dimensión
0940: * @param dimension
0941: */
0942: public void addDimension(int dimension) {
0943: int[] dimensions;
0944: int factor;
0945: int[] list;
0946: int queryDimensionsSize;
0947: int combination;
0948: String bits;
0949: int bitsLenght;
0950: int pos;
0951:
0952: dimensions = getQuery().getDimensions();
0953: queryDimensionsSize = dimensions.length;
0954: dimensionsCombinations = new LinkedList();
0955: for (combination = 1; combination <= (Math.pow(2,
0956: queryDimensionsSize) - 1); combination++) {
0957: bits = Integer.toBinaryString(combination);
0958: bitsLenght = bits.length();
0959: list = new int[bitsLenght];
0960: for (int index = 0; index < bitsLenght; index++) {
0961: pos = bitsLenght - 1 - index;
0962: factor = Character.digit(bits.charAt(pos), 10);
0963: if (factor == 1) {
0964: list[index] = factor * dimensions[index];
0965: } else {
0966: list[index] = INVALID_DIMENSION;
0967: }
0968: }
0969: dimensionsCombinations.add(list);
0970: }
0971: }
0972:
0973: /**
0974: * Incompleto. Es para agregar incrementalmente dimensiones
0975: * @param newDimensions
0976: */
0977: public void fillWithNewDimensions(LinkedList newDimensions) {
0978: /* Iterator iterator;
0979: LinkedList newDimensionsCombinations;
0980: int dimension;
0981:
0982: iterator = newDimensions.iterator();
0983: while (iterator.hasNext()) {
0984: dimension = ((Integer) iterator.next()).intValue();
0985:
0986:
0987: }*/
0988: ///todo: Falta completar resolucón incremental de dimensiones
0989: }
0990:
0991: /**
0992: * Devuelve un iterador para recorrer los contenidos de la estructura Cube
0993: * @return
0994: */
0995: public CubeIterator iterator() {
0996: return CubeIterator.on(this );
0997: }
0998:
0999: /**
1000: * Devuelve un iterador para recorrer los valores de una dimensión aplicando el criterio de ordenamiento
1001: * @param table
1002: * @param dimensionIndex
1003: * @return
1004: */
1005: public Iterator sortedIteratorFor(HashMap table, int dimensionIndex) {
1006: /*if ( getQuery().getDimensionRank()[dimensionIndex] > 0 ){
1007: TreeMap treeMap = new TreeMap(getQuery().entryComparatorFor(dimensionIndex));
1008: treeMap.entrySet().addAll(table.entrySet());
1009: return treeMap.entrySet().iterator();
1010: }
1011: else{*/
1012: TreeSet set;
1013: set = new TreeSet(getQuery().entryComparatorFor(
1014: getQuery().getDimensions()[dimensionIndex]));
1015: if (table != null) {
1016: set.addAll(table.entrySet());
1017: }
1018: return set.iterator();
1019: //}
1020: }
1021:
1022: /**
1023: * Retorna un iterador ordenado sobre los valores para una dimensión a partir de las coordenadas indicadas por
1024: * previousDimensions y values
1025: * @param dimension
1026: * @param previousDimensions
1027: * @param values
1028: * @return
1029: */
1030: public Iterator valuesFor(int dimension, int[] previousDimensions,
1031: Object[] values) {
1032: Object[] node;
1033: HashMap table;
1034:
1035: node = measuresAtDimensionsValues(previousDimensions, values);
1036: table = (HashMap) node[dimension];
1037:
1038: return sortedIteratorFor(table, dimension);
1039: }
1040:
1041: public Set[] getDimensionValues() {
1042: if (dimensionValues == null) {
1043: dimensionValues = new TreeSet[getFullDimensionsSize()];
1044: for (int i = 0; i < dimensionValues.length; i++) {
1045: dimensionValues[i] = new TreeSet();
1046: }
1047: }
1048: return dimensionValues;
1049: }
1050:
1051: private int getFullDimensionsSize() {
1052: return fullDimensionsSize;
1053: }
1054:
1055: /**
1056: * Realiza las operaciones posteriores a la carga de datos del cubo. Se calculan los average y los maximos
1057: * y minimos para totales.
1058: */
1059: public void afterFill() {
1060: if (emptyCube()) {
1061: root = newEmptyRoot();
1062: } else {
1063: int[] indexes = getAverageMetricsIndexes();
1064: if (indexes.length > 0) {
1065: setAverageValues(root, indexes);
1066: }
1067: indexes = getMinMaxFootersIndexes();
1068: if (indexes.length > 0) {
1069: int[] dimensionsOrder = getDimensionByGroupingOrder();
1070: for (int i = 0; i < dimensionsOrder.length; i++) {
1071: setMinMaxFooters(root, indexes, dimensionsOrder, 0);
1072: }
1073: }
1074: indexes = getCountDistinctIndexes();
1075: if (indexes.length > 0) {
1076: setNodesCountValues(root, indexes);
1077: }
1078: }
1079: if (!excludedNodes.isEmpty()) {
1080: Iterator it = dimensionsCombinations.iterator();
1081: while (it.hasNext()) {
1082: int[] dimensions = (int[]) it.next();
1083: if (excludedNodes.containsKey(dimensions))
1084: addExcludedNode((Object[]) excludedNodes
1085: .get(dimensions), dimensions);
1086: }
1087: }
1088: System.gc();
1089: System.gc();
1090: System.gc();
1091: System.gc();
1092: System.gc();
1093: }
1094:
1095: private int[] getCountDistinctIndexes() {
1096: Object[] metrics = definition.getMetrics();
1097: LinkedList indexes = new LinkedList();
1098: for (int i = 0; i < metrics.length; i++) {
1099: Object metric = metrics[i];
1100: if (metric instanceof ReportMetricSpec) {
1101: ReportMetricSpec metricSpec = (ReportMetricSpec) metric;
1102: if ((metricSpec.getAggregateType().getType() == CalculationType.COUNT_DISTINCT_TYPE && isMetricInQuery(i))) {
1103: indexes.add(new Integer(i + dimensionsSize));
1104: }
1105: }
1106: }
1107: return toIntArray(indexes);
1108: }
1109:
1110: private void setNodesCountValues(Object[] node, int[] indexes) {
1111: for (int j = 0; j < indexes.length; j++) {
1112: int index = indexes[j];
1113: node[index] = SharedFloat.newFrom(((Set) node[index])
1114: .size());
1115: }
1116: for (int i = 0; i < dimensionsSize; i++) {
1117: if (node[i] != null) {
1118: Map nodes = (Map) node[i];
1119: Iterator it = nodes.values().iterator();
1120: while (it.hasNext()) {
1121: Object[] childNode = (Object[]) it.next();
1122: setNodesCountValues(childNode, indexes);
1123: }
1124: }
1125: }
1126: }
1127:
1128: /**
1129: * Obtiene las dimensiones existentes en la query en el orden en el que agrupan. Es decir, desde el primer nivel
1130: * de row en el que se agrupan todas las dimensiones, hacia las columnas que despliegan las metricas.
1131: * @return
1132: */
1133: private int[] getDimensionByGroupingOrder() {
1134: int[] result = new int[query.getDimensions().length];
1135: int[] queryColumns = query.getColumns();
1136: int[] queryRows = query.getRows();
1137: System.arraycopy(queryRows, 0, result, 0, queryRows.length);
1138: System.arraycopy(queryColumns, 0, result, queryRows.length,
1139: queryColumns.length);
1140: return result;
1141: }
1142:
1143: /**
1144: * Retorna un nuevo nodo root vacío (solo con valores de metricas inicializados a 0).
1145: * @return
1146: */
1147: private Object[] newEmptyRoot() {
1148: Object[] result = new Object[arraySize];
1149: for (int i = dimensionsSize; i < result.length; i++) {
1150: result[i] = SharedFloat.newFrom(0);
1151: }
1152: return result;
1153: }
1154:
1155: /**
1156: * Retorna si el cubo que se lleno esta vacio o no.
1157: * @return true si el cubo esta vacio.
1158: */
1159: private boolean emptyCube() {
1160: int i = 0;
1161: for (; i < dimensionsSize; i++) {
1162: if (root[i] != null) {
1163: return false;
1164: }
1165: }
1166: for (; i < arraySize; i++) {
1167: if (!Float.isNaN(((SharedFloat) root[i]).floatValue())) {
1168: return false;
1169: }
1170: }
1171: return true;
1172: }
1173:
1174: /**
1175: * Una vez que esta hecho el cubo, recorre los nodos para buscar los maximos y minimos para los footers de grupo
1176: * que tengan seleccionada alguna de estas funciones. Para ello recorre recursivamente el cubo desde el ultimo
1177: * nivel hacia el que mas agrupa, seteando en cada nivel los maximos y minimos correspondientes.
1178: * @param node el nodo que se esta recorriendo
1179: * @param indexes los indices de las metricas que tienen funciones max y min en sus footers
1180: * @param modelIndexes los indices del modelo, desde el que mas agrupa hasta el ultimo.
1181: * @param modelIndex indice dentro del que estoy ubicado dentro de modelIndexes (se referenciara el indice
1182: * de la dimension como modelIndexes[modelIndex])
1183: */
1184: private void setMinMaxFooters(Object[] node, int[] indexes,
1185: int[] modelIndexes, int modelIndex) {
1186: if (modelIndex + 1 < modelIndexes.length) {
1187: Iterator iterator = ((Map) node[modelIndexes[modelIndex]])
1188: .entrySet().iterator();
1189: while (iterator.hasNext()) {
1190: Object[] childNode = (Object[]) ((Map.Entry) iterator
1191: .next()).getValue();
1192: for (int childIndex = modelIndex + 1; childIndex < modelIndexes.length; childIndex++) {
1193: setMinMaxFooters(childNode, indexes, modelIndexes,
1194: childIndex);
1195: }
1196: }
1197: }
1198: int nodeIndex = getNodeIndex(node, modelIndexes);
1199: if (nodeIndex == modelIndexes[modelIndex]) {
1200: for (int i = 0; i < indexes.length; i++) {
1201: int index = indexes[i];
1202: node[index] = obtainValue(
1203: (Map) node[modelIndexes[modelIndex]], index);
1204: }
1205: }
1206: //Si el indice de la dimension esta en Rows y si hay columns, setea los valores maximos y minimos para columns
1207: if (isInRows(nodeIndex) && getQuery().getColumns() != null
1208: && getQuery().getColumns().length > 0) {
1209: setColumnValues(node, indexes, modelIndexes, nodeIndex);
1210: }
1211: }
1212:
1213: /**
1214: * Dado un indice, retorna si ese indice se encuentra dentro de las row de la query.
1215: * @param dimensionIndex indice de la dimension que se busca
1216: * @return true si la dimension esta dentro de rows de la query
1217: */
1218: private boolean isInRows(int dimensionIndex) {
1219: for (int i = 0; i < getQuery().getRows().length; i++) {
1220: int row = getQuery().getRows()[i];
1221: if (dimensionIndex == row) {
1222: return true;
1223: }
1224: }
1225: return false;
1226: }
1227:
1228: /**
1229: * Setea los totales maximos y minimos para las columnas. Dentro del cube, un nodo que esta en row tiene el
1230: * despliegue de valores de las distintas combinaciones de column, para totales. Por ello, se deben recorrer
1231: * todos estos nodos, y buscar los valores maximos y minimos (se hace cuando se termino de cargar el cubo).
1232: * @param node nodo al que se le setean los maximos y minimos
1233: * @param indexes indices de las metricas que tienen maximos y minimos en footer.
1234: * @param modelIndexes indices de las dimensiones en el orden del modelo de la query
1235: * @param maxNodeIndex si el nodo agrupa en el nivel n, se pasara el indice de la dimension n+1 en orden de agrupacion
1236: */
1237: private void setColumnValues(Object[] node, int[] indexes,
1238: int[] modelIndexes, int maxNodeIndex) {
1239: for (int j = getQuery().getRows().length; j < modelIndexes.length; j++) {
1240: int modelIndex = modelIndexes[j];
1241: Iterator iterator = ((Map) node[modelIndex]).entrySet()
1242: .iterator();
1243: while (iterator.hasNext()) {
1244: Map.Entry entry = (Map.Entry) iterator.next();
1245: Vector dimensionsValues = new Vector();
1246: dimensionsValues.add(entry.getKey());
1247: Object[] childNode = (Object[]) entry.getValue();
1248: for (int i = 0; i < indexes.length; i++) {
1249: int footerIndex = indexes[i];
1250: childNode[footerIndex] = getFooterValueFor(node,
1251: dimensionsValues, footerIndex,
1252: modelIndexes, j, maxNodeIndex);
1253: }
1254: setSubColumnValues(node, childNode, dimensionsValues,
1255: indexes, modelIndexes, j + 1, maxNodeIndex, j);
1256: }
1257: }
1258: }
1259:
1260: /**
1261: * Setea los valores de todos los nodos intermedios en column recorriendolos recursivamente desde el que mas agrupa
1262: * al que menos (el que despliega en las metricas).
1263: * @param node nodo de Row al que se le estan seteando los valores
1264: * @param childNode nodo superior del modelo de column
1265: * @param dimensionsValues valores agrupados de los nodos anteriores de column
1266: * @param indexes indices de las metricas que usan max y min en totales
1267: * @param modelIndexes indices de las dimensiones en el orden del modelo de la query
1268: * @param columnIndex indice de la columna dentro de los model indexes (se referenciara modelIndexes[columnIndex]).
1269: * @param maxNodeIndex si el nodo agrupa en el nivel n, se pasara el indice de la dimension n+1 en orden de agrupacion
1270: * @param startingColumnIndex primera dimension en column que se esta considerando en la agrupacion de dimensionsValues
1271: */
1272: private void setSubColumnValues(Object[] node, Object[] childNode,
1273: Vector dimensionsValues, int[] indexes, int[] modelIndexes,
1274: int columnIndex, int maxNodeIndex, int startingColumnIndex) {
1275: if (columnIndex < modelIndexes.length) {
1276: Iterator iterator = ((Map) childNode[modelIndexes[columnIndex]])
1277: .entrySet().iterator();
1278: while (iterator.hasNext()) {
1279: Map.Entry entry = (Map.Entry) iterator.next();
1280: Vector clonedValues = (Vector) dimensionsValues.clone();
1281: clonedValues.add(entry.getKey());
1282: Object[] subNode = (Object[]) entry.getValue();
1283: for (int i = 0; i < indexes.length; i++) {
1284: int footerIndex = indexes[i];
1285: subNode[footerIndex] = getFooterValueFor(node,
1286: clonedValues, footerIndex, modelIndexes,
1287: startingColumnIndex, maxNodeIndex);
1288: }
1289: setSubColumnValues(node, subNode, clonedValues,
1290: indexes, modelIndexes, columnIndex + 1,
1291: maxNodeIndex, startingColumnIndex);
1292: }
1293: }
1294: }
1295:
1296: /**
1297: * Obtiene el valor maximo o minimo para una column segun los valores de su nodo row
1298: * @param node nodo row que se esta considerando
1299: * @param dimensionValues valores agrupados de las dimensiones en column. Se agrupan los valores desde el nivel inicial
1300: * @param index indice de la metrica
1301: * @param modelIndexes indices de las dimensiones en el orden del modelo de la query
1302: * @param columnIndex indice de la columna dentro de los model indexes (se referenciara modelIndexes[columnIndex]).
1303: * @param maxNodeIndex si el nodo agrupa en el nivel n, se pasara el indice de la dimension n+1 en orden de agrupacion
1304: * @return
1305: */
1306: private SharedFloat getFooterValueFor(Object[] node,
1307: Vector dimensionValues, int index, int[] modelIndexes,
1308: int columnIndex, int maxNodeIndex) {
1309: SharedFloat result = SharedFloat.newFrom(Float.NaN);
1310: Iterator iterator = ((Map) node[maxNodeIndex]).entrySet()
1311: .iterator();
1312: while (iterator.hasNext()) {
1313: Map.Entry entry = (Map.Entry) iterator.next();
1314: Map subNodeValues = (Map) ((Object[]) entry.getValue())[modelIndexes[columnIndex]];
1315: Object[] columnNode = (Object[]) subNodeValues
1316: .get(dimensionValues.elementAt(0));
1317: for (int i = 1; i < dimensionValues.size()
1318: && columnNode != null
1319: && columnIndex + i < modelIndexes.length; i++) {
1320: Object dimensionValue = dimensionValues.elementAt(i);
1321: subNodeValues = (Map) columnNode[modelIndexes[columnIndex
1322: + i]];
1323: columnNode = (Object[]) subNodeValues
1324: .get(dimensionValue);
1325: }
1326: if (columnNode != null) {
1327: result = (SharedFloat) groupFooterStrategies[index
1328: - dimensionsSize].operate(columnNode, index,
1329: result, null);
1330: } else {
1331: //Simula pasarle un nodo por parametro. Dentro del stategy accede a object[0], y le dara el mismo valor.
1332: result = (SharedFloat) groupFooterStrategies[index
1333: - dimensionsSize].operate(
1334: new Object[] { result }, 0, null, null);
1335: }
1336: }
1337: return result;
1338: }
1339:
1340: /**
1341: * Obtiene el mayor indice de grupo de un nodo. Por ejemplo si un estamos considerando un nodo que tiene los valores
1342: * de la primer dimension en row, retornará el indice de la segunda dimension en row.
1343: * @param node
1344: * @param modelIndexes
1345: * @return
1346: */
1347: private int getNodeIndex(Object[] node, int[] modelIndexes) {
1348: for (int i = 0; i < modelIndexes.length; i++) {
1349: int modelIndex = modelIndexes[i];
1350: if (node[modelIndex] != null) {
1351: return modelIndex;
1352: }
1353: }
1354: return modelIndexes[0];
1355: }
1356:
1357: /**
1358: * Dado un conjunto de nodos y un indice de metrica, retorna el valor para el total de esa metrica, calculando,
1359: * segun corresponda a la metrica, maximo o minimo.
1360: * @param nodeValues subnodos
1361: * @param index indice de la metrica
1362: * @return
1363: */
1364: private SharedFloat obtainValue(Map nodeValues, int index) {
1365: SharedFloat result = SharedFloat.newFrom(Float.NaN);
1366: for (Iterator iterator = nodeValues.entrySet().iterator(); iterator
1367: .hasNext();) {
1368: Object[] node = (Object[]) ((Map.Entry) iterator.next())
1369: .getValue();
1370: result = (SharedFloat) groupFooterStrategies[index
1371: - dimensionsSize]
1372: .operate(node, index, result, null);
1373: }
1374: return result;
1375: }
1376:
1377: /**
1378: * Busca los indices de las metricas que utilizan maximos y minimos
1379: * @return
1380: */
1381: private int[] getMinMaxFootersIndexes() {
1382: Object[] metrics = definition.getMetrics();
1383: LinkedList indexes = new LinkedList();
1384: for (int i = 0; i < metrics.length; i++) {
1385: Object metric = metrics[i];
1386: if (metric instanceof ReportMetricSpec) {
1387: ReportMetricSpec metricSpec = (ReportMetricSpec) metric;
1388: if ((metricSpec.getGroupFooterType().getType() == CalculationType.MAX_TYPE || metricSpec
1389: .getGroupFooterType().getType() == CalculationType.MIN_TYPE)
1390: && isMetricInQuery(i)) {
1391: indexes.add(new Integer(i + dimensionsSize));
1392: }
1393: }
1394: }
1395: return toIntArray(indexes);
1396: }
1397:
1398: /**
1399: * Setea los valores promedio para un nodo
1400: * @param node nodo a setear
1401: * @param indexes indices de las metricas que utilizan average
1402: */
1403: private void setAverageValues(Object[] node, int[] indexes) {
1404: setAverageValueForNode(node, indexes);
1405: int[] dimensions = query.getDimensions();
1406: for (int i = 0; i < dimensions.length; i++) {
1407: int dimension = dimensions[i];
1408: if (node[dimension] != null) {
1409: setAverageValueForChild((Map) node[dimension], indexes);
1410: }
1411: }
1412: }
1413:
1414: /**
1415: * Recorre los subnodos de un nodo, seteando recursivamente los valores promedio
1416: * @param map subnodos
1417: * @param indexes
1418: */
1419: private void setAverageValueForChild(Map map, int[] indexes) {
1420: for (Iterator iterator = map.entrySet().iterator(); iterator
1421: .hasNext();) {
1422: Map.Entry entry = (Map.Entry) iterator.next();
1423: setAverageValues((Object[]) entry.getValue(), indexes);
1424: }
1425: }
1426:
1427: /**
1428: * Setea especificamente el valor del average de un nodo, diferenciando si este es o no total.
1429: * @param node
1430: * @param indexes
1431: */
1432: private void setAverageValueForNode(Object[] node, int[] indexes) {
1433: for (int i = 0; i < indexes.length; i++) {
1434: int index = indexes[i];
1435: if (isLastLevel(node)
1436: && metricStrategies[index] instanceof AverageStrategy) {
1437: node[index + dimensionsSize] = ((SharedFloat) node[index
1438: + dimensionsSize]).div((SharedFloat) node[index
1439: + dimensionsSize + metricsSize / 2]);
1440: } else if (groupFooterStrategies[index] instanceof AverageStrategy) {
1441: node[index + dimensionsSize] = ((SharedFloat) node[index
1442: + dimensionsSize]).div((SharedFloat) node[index
1443: + dimensionsSize + metricsSize / 2]);
1444: }
1445: }
1446: }
1447:
1448: /**
1449: * Retorna verdadero si el nodo corresponde al ultimo nivel de grupo, es decir, no agrupa a otras dimensiones.
1450: * @param node
1451: * @return
1452: */
1453: private boolean isLastLevel(Object[] node) {
1454: for (int i = 0; i < node.length && i < getDimensionsSize(); i++) {
1455: if (node[i] != null) {
1456: return false;
1457: }
1458: }
1459: return true;
1460: }
1461:
1462: public Object[] getMetricsValuesAt(int[] dimensions, Object[] values) {
1463: Object[] node;
1464: int index;
1465: int dimensionsLenght;
1466:
1467: node = root;
1468: dimensionsLenght = dimensions.length;
1469: for (index = 0; index < dimensionsLenght; index++) {
1470: node = atDimensionValueFor(values[index],
1471: dimensions[index], node);
1472: }
1473: return node;
1474: }
1475:
1476: private Object[] atDimensionValueFor(Object value, int dimension,
1477: Object[] node) {
1478: HashMap dict;
1479: Object o;
1480: if (node == null) {
1481: return node;
1482: }
1483: if ((node[dimension] instanceof HashMap)) {
1484: dict = (HashMap) node[dimension];
1485: } else {
1486: return null;
1487: }
1488: o = dict.get(value);
1489: return (Object[]) o;
1490: }
1491:
1492: public Set getDimensionValues(int index) throws InfoException {
1493: if (getDimensionValues()[index].isEmpty() && pivot != null) {
1494: getDimensionValues()[index].addAll(pivot
1495: .getDimensionValues(index));
1496: }
1497: return getDimensionValues()[index];
1498: }
1499:
1500: protected void setPivot(Pivot pivot) {
1501: this .pivot = pivot;
1502: }
1503:
1504: public MetricCalculationStrategy[] getMetricStrategies() {
1505: return metricStrategies;
1506: }
1507:
1508: }
|