Package org.objectweb.speedo.query.jdo.parser

Source Code of org.objectweb.speedo.query.jdo.parser.SelectGroupByVisitor

/**
* Speedo: an implementation of JDO compliant personality on top of JORM generic
* I/O sub-system.
* Copyright (C) 2001-2005 France Telecom R&D
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*
*
* Authors: S.Chassande-Barrioz.
* Created on 3 fevr. 2005
*
*/
package org.objectweb.speedo.query.jdo.parser;

import org.objectweb.jorm.api.PMapper;
import org.objectweb.jorm.metainfo.api.ClassRef;
import org.objectweb.jorm.metainfo.api.GenClassRef;
import org.objectweb.jorm.metainfo.api.Reference;
import org.objectweb.jorm.type.api.PType;
import org.objectweb.medor.api.Field;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.expression.api.Expression;
import org.objectweb.medor.expression.api.ExpressionException;
import org.objectweb.medor.expression.api.Operator;
import org.objectweb.medor.filter.api.FieldOperand;
import org.objectweb.medor.filter.lib.Avg;
import org.objectweb.medor.filter.lib.BasicFieldOperand;
import org.objectweb.medor.filter.lib.Count;
import org.objectweb.medor.filter.lib.Max;
import org.objectweb.medor.filter.lib.Min;
import org.objectweb.medor.filter.lib.Sum;
import org.objectweb.medor.query.api.PropagatedField;
import org.objectweb.medor.query.api.QueryNode;
import org.objectweb.medor.query.api.QueryTree;
import org.objectweb.medor.query.api.QueryTreeField;
import org.objectweb.medor.query.jorm.lib.ClassExtent;
import org.objectweb.medor.query.jorm.lib.JormQueryTreeHelper;
import org.objectweb.medor.query.jorm.lib.PNameField;
import org.objectweb.medor.query.lib.Nest;
import org.objectweb.medor.query.lib.SelectProject;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.speedo.mim.api.HomeItf;
import org.objectweb.speedo.query.jdo.JDOQueryDefinitionImpl;
import org.objectweb.speedo.query.jdo.JDOQueryEvalContext;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;

/**
* This visitor parses select and group by clauses in order to build the
* projected field of a MEDOR query.
*
* @author S.Chassande-Barrioz
*/
public class SelectGroupByVisitor {

    /**
     * key word for the count operator
     */
    private static final String COUNT = "count";

    /**
     * key word for the sum operator
     */
    private static final String SUM = "sum";

    /**
     * key word for the min operator
     */
    private static final String MIN = "min";

    /**
     * key word for the max operator
     */
    private static final String MAX = "max";

    /**
     * key word for the avg operator
     */
    private static final String AVG = "avg";

    /**
     * key word for the distrinct constraint
     */
    private static final String DISTINCT = "distinct";

    /**
     * key word for the unique constraint
     */
    private static final String UNIQUE = "unique";

    /**
     * list of keywords
     */
    private static final String[] keywords = new String[] {
            COUNT.toLowerCase(), //0
            COUNT.toUpperCase(), //1
            SUM.toLowerCase(), //2
            SUM.toUpperCase(), //3
            MIN.toLowerCase(), //4
            MIN.toUpperCase(), //5
            MAX.toLowerCase(), //6
            MAX.toUpperCase(), //7
            AVG.toLowerCase(), //8
            AVG.toUpperCase(), //9
            DISTINCT.toLowerCase(), //10
            DISTINCT.toUpperCase(), //11
            "(", //12
            ")", //13
            ",", //14
            "as", //15
            "AS", //16
            UNIQUE.toLowerCase(), //17
            UNIQUE.toUpperCase()  //18
    };

    /**
     * Retrieves the String representation of the function identifier
     */
    private final static String getFunctionAsString(int functionIdx) {
        switch (functionIdx / 2) {
        case 0:
            return "COUNT";
        case 1:
            return "SUM";
        case 2:
            return "MIN";
        case 3:
            return "MAX";
        case 4:
            return "AVG";
        default:
            return "UNKNOWN";
        }

    }

    /**
     * SelectProjet where the simple (not aggregat operation) selected fields
     * must be projected.
     */
    private QueryNode top;

    /**
     * The query tree corresponding to the query
     */
    private QueryTree qt;

    /**
     * The JORM PMapper permitting to fetch PClassMapping for prefetching
     */
    private PMapper mapper;
   
    private ClassLoader classloader;

    /**
     * The visitor of variable used in Speedo for building the query. The
     * visitor is used here for building paths of the select. Indeed the select
     * clause can contains navigation path.
     */
    private SpeedoQLVariableVisitor sqvv;

    /**
     * The QueryEvalContext used in Speedo for evaluate the MEDOR query. During
     * the visit of select clause, the algorithm determines if prefetching is
     * possible and on which class. If prefetching is possible, the
     * PClassMapping of the prefetched class and the pname index are assigned on
     * the #qec.
     */
    private JDOQueryEvalContext qec;

    /**
     * indicate if a distinct constraint is specified in the select clause This
     * field is filled after the call of the parse(String) method.
     */
    private boolean unique = false;

    /**
     * indicate if a distinct constraint is specified in the select clause This
     * field is filled after the call of the parse(String) method.
     */
    private boolean distinct = false;

    /**
     * indicate if the select clause contains aggregat operator(s) This field is
     * filled after the call of the parse(String) method.
     */
    private boolean hasAggregate = false;

    /**
     * contains the MEDOR object permitting to build the projection part of the
     * query. MEDOR object type can be one of the following: - Field : it means
     * the selected part is a simple navigation (this, this.a.f1, this.a.myB,
     * ...) - Expression: it means the selected part is a calculated field such
     * as aggregate function (AVG(this.a.f1), this.f1 + this.f2, ...).
     *
     * This field is filled after the call of the parse(String) method.
     */
    private ArrayList selectfields = new ArrayList();

    /**
     * contains the alias name (String) of the projected fields. The content of
     * this field is linked to the #selectfields field. A null value in the list
     * means the corresponding selected fields (same index) has no alias.
     *
     * This field is filled after the call of the parse(String) method.
     */
    private ArrayList aliases = new ArrayList();

    /**
     * contains the list of grouped fields. The grouped fields are the one used
     * in an aggregat operator.
     *
     * This field is filled after the call of the parse(String) method.
     */
    private ArrayList groupedFields = new ArrayList();

    /**
     * contains the list of fields specified in the group by clause.
     *
     * This field is filled after the call of the parse(String) method.
     */
    private ArrayList groupByFields = new ArrayList();

    private ArrayList usedFields = new ArrayList();
   
    private ArrayList selectFieldTypes = new ArrayList();

    /**
     * @param qt
     * @param sp
     * @param mapper
     * @param sqvv
     * @param qd
     * @param qec
     */
    public SelectGroupByVisitor(SelectProject sp,
            QueryTree qt,
            PMapper mapper,
            SpeedoQLVariableVisitor sqvv,
            JDOQueryEvalContext qec,
            ClassLoader classloader) {
        super();
        this.qt = qt;
        this.top = sp;
        this.mapper = mapper;
        this.sqvv = sqvv;
        this.qec = qec;
        this.classloader = classloader;
    }
    public SelectGroupByVisitor(SelectProject sp,
            QueryTree qt,
            SpeedoQLVariableVisitor sqvv,
            ClassLoader classloader) {
        this(sp, qt, null, sqvv, null, classloader);
    }

    public Class[] getSelectFieldTypes() {
        return (Class[]) selectFieldTypes.toArray(new Class[selectFieldTypes.size()]);
    }
   
    /**
     * Visit the select and Groupby clause
     * @param qd is the definition of the query
     */
    public void visit(JDOQueryDefinitionImpl qd)
            throws SpeedoException {
        try {
            _visit(qd);
        } catch (ExpressionException e) {
            throw new SpeedoException(e);
        } catch (MedorException e) {
            throw new SpeedoException(e);
        }
    }
   
    /**
     * Visit the select and Groupby clause
     * @param qd is the definition of the query
     */
    private void _visit(JDOQueryDefinitionImpl qd)
        throws SpeedoException, MedorException, ExpressionException {
        String select = qd.getResult();
        String groupby = qd.getGrouping();
        boolean withPrefetch = qd.withPrefetch();
        if (select == null || select.length() == 0) {
            select = "this";
        }
        parseSelect(select);
        parseGroupBy(groupby);

        //Prefetching or not
        if (!hasAggregate //not aggregation
                && withPrefetch //Prefetching asked
                && selectfields.size() == 1 //only one part in the select
          ) {
            addPrefetchField(qd);
        }
        if (hasAggregate) {
            top = addAggregateNode();
        }
        top.setDistinct(distinct);
        projectFields();
        if (qec != null) {
            qec.query = top;
        }
    }
   
    /**
     * Add the prefetch field to the current query tree
     * @param qd is the definition of the query
     */
    private void addPrefetchField(JDOQueryDefinitionImpl qd) throws MedorException {
        Field f = ((PropagatedField) selectfields.get(0)).getOriginFields()[0];
        if (f instanceof PNameField) {
            PNameField pnf = (PNameField) f;
            if (pnf.isClassPName() && !pnf.isInGenClass()) {
                //Add prefeched fields
                ClassExtent ce = (ClassExtent) pnf.getQueryTree();
                String prefetchedClassName = ce.getJormName();
                HomeItf sh = (HomeItf) mapper.lookup(prefetchedClassName);
                if (sh.getPrefetchOnQuery()) {
                    qec.pcm = sh;
                    JormQueryTreeHelper.addPrefetchFields(ce, qt, top,
                                qd.getIncludeSubClasses());
                    qec.pnIndex = top.getTupleStructure().getSize() + 1;
                }
            }//else the selected field is a class identifier
        } //else the selected field is not an identifier or a reference
    }
   
    /**
     * @return an aggregate node for aggregate operators. the returned node must
     * become the root the query tree.
     */
    private Nest addAggregateNode() throws SpeedoException, MedorException {
        Map old2newFields = new HashMap();
        //project used fields on the QueryNode #top
        for (int i = 0; i < usedFields.size(); i++) {
            QueryTreeField qtf = (QueryTreeField) usedFields.get(i);
            Field newf = top.addPropagatedField(qtf.getName(), qtf
                        .getType(), new QueryTreeField[] { qtf });
                old2newFields.put(qtf, newf);
        }
        //replace the use of old fields in 'grouped' by the new projected
        replaceFieldsInList(groupedFields, old2newFields);
        //replace the use of old fields in 'groupby' by the new projected
        replaceFieldsInList(groupByFields, old2newFields);
        //replace the use of old fields in 'selected' by the new projected
        replaceFieldsInList(selectfields, old2newFields);
        return new Nest((QueryTreeField[]) groupedFields
                    .toArray(new QueryTreeField[groupedFields.size()]),
                    "grouped_fields", (QueryTreeField[]) groupByFields
                            .toArray(new QueryTreeField[groupByFields
                                    .size()]), "aggregate_node");
        //The nest becomes the top node, selected field will add on it
    }
   
    /**
     * Projects the fields defined in the select clause on the top node.
     */
    private void projectFields()
      throws SpeedoException, MedorException, ExpressionException {
        //project field on the #top query node
        for (int i = 0; i < selectfields.size(); i++) {
            Object o = selectfields.get(i);
            Field fieldOnTop = null;
            String fieldName = (String) aliases.get(i);
            if (o instanceof Expression) {
                if (fieldName == null || fieldName.length() == 0) {
                    fieldName = "field_" + i;
                }
                Expression e = (Expression) o;
                e.compileExpression();
                fieldOnTop = top.addCalculatedField(fieldName, e.getType(), e);
            } else if (o instanceof Field) {
                Field f = (Field) o;
                if (fieldName == null || fieldName.length() == 0) {
                    fieldName = f.getName();
                }
                if (!groupByFields.contains(f) || (top instanceof Nest)) {
                    fieldOnTop = top.addPropagatedField(fieldName, f.getType(),
                            new QueryTreeField[] { (QueryTreeField) f });
                } else {
                    String fn = top.getName();
                    if (fn == null || fn.length() == 0) {
                        fn = "";
                    } else {
                        fn += ".";
                    }
                    fn += f.getName();
                    fieldOnTop = top.getTupleStructure().getField(fn);
                }
            }
            //Compute the field type
            selectFieldTypes.add(getFieldClass(fieldOnTop));
        }
    }
   
    /**
     * Computes the type of a field.
     */
    private Class getFieldClass(Field field) throws SpeedoException {
        PType ptype = field.getType();
        String className = null;
        switch(ptype.getTypeCode()) {
        case PType.TYPECODE_BIGDECIMAL:
            return BigDecimal.class;
        case PType.TYPECODE_BIGINTEGER:
            return BigInteger.class;
        case PType.TYPECODE_BOOLEAN:
        case PType.TYPECODE_OBJBOOLEAN:
            return Boolean.class;
        case PType.TYPECODE_BYTE:
        case PType.TYPECODE_OBJBYTE:
            return Byte.class;
        case PType.TYPECODE_BYTEARRAY:
            return Byte[].class;
        case PType.TYPECODE_CHAR:
        case PType.TYPECODE_OBJCHAR:
            return Character.class;
        case PType.TYPECODE_CHARARRAY:
            return Character[].class;
        case PType.TYPECODE_DATE:
            return Date.class;
        case PType.TYPECODE_DOUBLE:
        case PType.TYPECODE_OBJDOUBLE:
            return Double.class;
        case PType.TYPECODE_FLOAT:
        case PType.TYPECODE_OBJFLOAT:
            return Float.class;
        case PType.TYPECODE_INT:
        case PType.TYPECODE_OBJINT:
            return Integer.class;
        case PType.TYPECODE_LONG:
        case PType.TYPECODE_OBJLONG:
            return Long.class;
        case PType.TYPECODE_SERIALIZED:
            return Serializable.class;
        case PType.TYPECODE_SHORT:
        case PType.TYPECODE_OBJSHORT:
            return Integer.class;
        case PType.TYPECODE_STRING:
            return String.class;

        case PType.TYPECODE_REFERENCE:
            if (field instanceof PropagatedField) {
                Field f = ((PropagatedField) field).getOriginFields()[0];
                if (f instanceof PNameField) {
                    PNameField pnf = (PNameField) f;
                    if (pnf.isClassPName()) {
                        if (pnf.isInGenClass()) {
                            //identifier of the genclass
                            className = pnf.getGenClassRef().getGenClassId();
                        } else {
                            //identifier of a class
                            className = pnf.getMetaObjectClass().getFQName();
                        }
                    } else {
                        Reference ref = pnf.getReference();
                        if (ref instanceof ClassRef) {
                            //reference to a class
                            className = ((ClassRef) ref).getMOClass().getFQName();
                        } else if (ref instanceof GenClassRef) {
                            //reference to a genclass
                            className = ((GenClassRef) ref).getGenClassId();
                        }
                    }
                }
            }
            break;
        default:
            className = ptype.getJavaName();
        }
        if (className == null) {
            throw new SpeedoException(
                    "Type '" + ptype.getJavaName()
                    + "' not found for projected field:"
                    + field.getName());
        } else {
            try {
                return classloader.loadClass(className);
            } catch (ClassNotFoundException e) {
                throw new SpeedoException(
                        "Type '" + className
                        + "' not found for projected field:"
                        + field.getName(), e);
            }
        }
    }

    /**
     * Replaces the list elements by the associated value from the map.
     * If an element of the list is an Expression (MEDOR), the use of Fields
     * in the Expression is replaced.
     * @param list
     *            is the list containg elements to replace
     * @param old2newElement
     *            is the table associating the old element (to replace) to the
     *            new element.
     */
    private void replaceFieldsInList(ArrayList list, Map old2newElement)
            throws SpeedoException {
        for (int i = 0; i < list.size(); i++) {
            Object old = list.get(i);
            Object neo = old2newElement.get(old);
            if (neo != null && old != neo) {
                list.set(i, neo);
            } else if (old instanceof Expression) {
                replaceFieldsInExpression((Expression) old, old2newElement);
            }
        }
    }

    /**
     * Replaces field usage in expression.
     * @param e is the expression to treat
     * @param old2newElement
     *            is the table associating the old element (to replace) to the
     *            new element.
     * @throws SpeedoException
     */
    private void replaceFieldsInExpression(Expression e, Map old2newElement)
            throws SpeedoException {
        if (e instanceof FieldOperand) {
            FieldOperand fo = (FieldOperand) e;
            Field old = fo.getField();
            Field neo = (Field) old2newElement.get(old);
            if (neo != null && old != neo) {
                ((FieldOperand) e).setField(neo);
            }
        } else if (e instanceof Operator) {
            Operator operator = (Operator) e;
            for (int i = 0; i < operator.getOperandNumber(); i++) {
                replaceFieldsInExpression(operator.getExpression(i),
                        old2newElement);
            }
        }
    }

    /**
     * Parses a select clause (with or without 'SELECT' keyword).
     *
     * @see #selectfields, #aliases, #groupedFields, #distinct, #hasAggregate .
     *      It
     * @param select
     * @throws SpeedoException
     */
    private void parseSelect(String select) throws SpeedoException {
        StringTokenizer st = new StringTokenizer(select, " (,)", true);
        Stack stack = new Stack();
        while (st.hasMoreTokens()) {
            String token = st.nextToken().trim();
            if (token.length() == 0) {
                continue;
            }
            if (token.equalsIgnoreCase("select")) {
                continue;
            }
            int keywordIdx = isKeyword(token);
            if (keywordIdx < 0) {
                parseFieldExpression(token, stack);
            } else if (keywordIdx < 10) {
                //Aggregate operator
                stack.push(new Integer(keywordIdx));
            } else if (keywordIdx == 10 || keywordIdx == 11) {
                //distinct
                if (stack.size() > 0) {
                    //the distinct is used in aggregate operator
                    stack.push(new Boolean(true));
                } else {
                    distinct = true;
                }
            } else if (keywordIdx == 17 || keywordIdx == 18) {
                //unique
                unique = true;
            } else if (keywordIdx == 14) {
                parseComma(stack);
            } else if (keywordIdx == 12) {
                //(
                stack.push(new Integer(keywordIdx));
            } else if (keywordIdx == 13) {
                //)
                parseRightParenthesis(stack);
            }
        }
        if (stack.size() == 1) {
            //There is only one element in the select clause
            selectfields.add(stack.pop());
            aliases.add(null);
        }
    }
    private void parseFieldExpression(String token, Stack stack) throws SpeedoException {
        //field expression
        if (token.equals("*")) {
            token = "this";
        }
        Field f = sqvv.getField(token);
        if (!usedFields.contains(f)) {
            usedFields.add(f);
        }
        stack.push(f);
    }
   
    private void parseComma(Stack stack) throws SpeedoException {
        if (stack.size() > 0) {
            Object o1 = stack.pop();
            if (stack.size() > 1) {
                //with an alias
                Object o2 = stack.pop();
                if (stack.size() > 2) {
                    //not the end of the selected field
                    stack.push(o2);
                    stack.push(o1);
                } else  if (!(o1 instanceof String)
                        || !(o2 instanceof Expression)) {
                    throw new SpeedoException(
                            "Malformed selected field "
                                    + (selectfields.size() + 1));
                } else {
                  selectfields.add(o2);
                  aliases.add(o1);
                }
            } else {
                //without an alias
                selectfields.add(o1);
                aliases.add(null);
            }
        } else {
            throw new SpeedoException(
                    "Empty definition of the selected field "
                            + (selectfields.size() + 1));
        }
    }   

    private void parseRightParenthesis(Stack stack) throws SpeedoException {
        ArrayList operands = new ArrayList();
        boolean isOperand = true;
        Integer functionIdx = null;
        //pop all operand until the '(' and the function identifier
        // (Integer)
        boolean distinctOnOperand = false;
        do {
            Object o = stack.pop();
            isOperand = !new Integer(12).equals(o);
            if (isOperand) {
                //register the operand
                operands.add(o);
            } else {
                if (stack.peek() instanceof Boolean) {
                    distinctOnOperand = ((Boolean) stack.pop())
                      .booleanValue();
                }
                // fetch the function identifier
                functionIdx = (Integer) stack.pop();
            }
        } while (isOperand);
        Expression e;
        //Verify the operand number
        if (functionIdx.intValue() > 0 && functionIdx.intValue() < 10) {
            if (operands.size() != 1) {
                throw new SpeedoException(
                        "Bad number of operand for the function "
                                + getFunctionAsString(functionIdx
                                        .intValue())
                                + ", expected 1 operand, found "
                                + operands.size());
            }
        }
        //instanciate the medor Operator corresponding to the function
        Object operand = operands.get(0);
        if (operand instanceof Expression) {
            e = (Expression) operand;
        } else if (operand instanceof Field) {
            e = new BasicFieldOperand((Field) operand);
        } else {
            throw new SpeedoException("Unexpect operand: " + operand);
        }
        getFieldsFromExpression(e, groupedFields);
        hasAggregate = true;
        switch (functionIdx.intValue() / 2) {
        case 0:
            e = new Count(e, distinctOnOperand);
            break;
        case 1:
            e = new Sum(e, distinctOnOperand);
            break;
        case 2:
            e = new Min(e, distinctOnOperand);
            break;
        case 3:
            e = new Max(e, distinctOnOperand);
            break;
        case 4:
            e = new Avg(e, distinctOnOperand);
            break;
        default:
            throw new SpeedoException("Unknown function identifier: "
                    + functionIdx.intValue());
        }
        stack.push(e);
    }

    /**
     * Parses a groupby clause (with or without 'GROUP BY' keyword).
     *
     * @see #selectfields, #aliases, #groupedFields, #distinct, #hasAggregate .
     *      It
     * @param select
     * @throws SpeedoException
     */
    private void parseGroupBy(String groupby) throws SpeedoException {
        if (groupby == null || groupby.length() == 0) {
            return;
        }
        StringTokenizer st = new StringTokenizer(groupby, ",", false);
        while (st.hasMoreTokens()) {
            groupByFields.add(sqvv.getField(st.nextToken().trim()));
        }
        for (int i = 0; i < groupByFields.size(); i++) {
            Field f = (Field) groupByFields.get(i);
            if (!usedFields.contains(f)) {
                usedFields.add(f);
            }
        }
    }

    /**
     * Parses an expression and register the list of Fiefd used in the
     * expression.
     *
     * @param e
     *            is the expression to parse
     * @param result
     *            is the list to fill with Field used in the expression
     */
    private void getFieldsFromExpression(Expression e, List result) {
        if (e instanceof FieldOperand) {
            Field f = ((FieldOperand) e).getField();
            if (!result.contains(f)) {
                result.add(f);
            }
        } else if (e instanceof Operator) {
            Operator operator = (Operator) e;
            for (int i = 0; i < operator.getOperandNumber(); i++) {
                getFieldsFromExpression(operator.getExpression(i), result);
            }
        }
    }

    private int isKeyword(String str) {
        if (str == null || str.length() == 0) {
            return -1;
        }
        for (int i = 0; i < keywords.length; i++) {
            if (keywords[i].equals(str)) {
                return i;
            }
        }
        return -1;
    }
}
TOP

Related Classes of org.objectweb.speedo.query.jdo.parser.SelectGroupByVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.