com.google.gwtorm.nosql.IndexFunctionGen.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwtorm.nosql.IndexFunctionGen.java

Source

// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gwtorm.nosql;

import com.google.gwtorm.schema.ColumnModel;
import com.google.gwtorm.schema.QueryModel;
import com.google.gwtorm.schema.QueryParser;
import com.google.gwtorm.schema.Util;
import com.google.gwtorm.server.CodeGenSupport;
import com.google.gwtorm.server.GeneratedClassLoader;
import com.google.gwtorm.server.OrmException;

import org.antlr.runtime.tree.Tree;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/** Generates {@link IndexFunction} implementations. */
class IndexFunctionGen<T> implements Opcodes {
    private static final Type string = Type.getType(String.class);
    private static final Type object = Type.getType(Object.class);
    private static final Type indexKeyBuilder = Type.getType(IndexKeyBuilder.class);

    private final GeneratedClassLoader classLoader;
    private final QueryModel query;
    private final List<QueryModel.OrderBy> myFields;
    private final Class<T> pojo;
    private final Type pojoType;

    private ClassWriter cw;
    private String superTypeName;
    private String implClassName;
    private String implTypeName;

    IndexFunctionGen(final GeneratedClassLoader loader, final QueryModel qm, final Class<T> t) {
        classLoader = loader;
        query = qm;

        myFields = new ArrayList<QueryModel.OrderBy>();

        // Only add each parameter column once, but in the order used.
        // This avoids a range test on the same column from duplicating
        // the data in the index record.
        //
        for (ColumnModel m : leaves(query.getParameters())) {
            QueryModel.OrderBy o = new QueryModel.OrderBy(m, false);
            if (!myFields.contains(o)) {
                myFields.add(o);
            }
        }

        // Skip ORDER BY columns that match with the parameters, then
        // add anything else onto the end.
        //
        int p = 0;
        Iterator<QueryModel.OrderBy> orderby = orderByLeaves(query.getOrderBy()).iterator();
        while (p < myFields.size() && orderby.hasNext()) {
            QueryModel.OrderBy o = orderby.next();
            ColumnModel c = o.column;
            if (!myFields.get(p).equals(o)) {
                myFields.add(o);
                break;
            }
            p++;
        }
        while (orderby.hasNext()) {
            myFields.add(orderby.next());
        }

        pojo = t;
        pojoType = Type.getType(pojo);
    }

    private static List<ColumnModel> leaves(List<ColumnModel> in) {
        ArrayList<ColumnModel> r = new ArrayList<ColumnModel>(in.size());
        for (ColumnModel m : in) {
            if (m.isNested()) {
                r.addAll(m.getAllLeafColumns());
            } else {
                r.add(m);
            }
        }
        return r;
    }

    private static List<QueryModel.OrderBy> orderByLeaves(List<QueryModel.OrderBy> in) {
        ArrayList<QueryModel.OrderBy> r = new ArrayList<QueryModel.OrderBy>(in.size());
        for (QueryModel.OrderBy m : in) {
            if (m.column.isNested()) {
                for (ColumnModel c : m.column.getAllLeafColumns()) {
                    r.add(new QueryModel.OrderBy(c, m.descending));
                }
            } else {
                r.add(m);
            }
        }
        return r;
    }

    IndexFunction<T> create() throws OrmException {
        init();
        implementConstructor();
        implementGetName();
        implementIncludes();
        implementEncode();
        cw.visitEnd();
        classLoader.defineClass(implClassName, cw.toByteArray());

        try {
            final Class<?> c = Class.forName(implClassName, true, classLoader);
            return cast(c.newInstance());
        } catch (InstantiationException e) {
            throw new OrmException("Cannot create new encoder", e);
        } catch (IllegalAccessException e) {
            throw new OrmException("Cannot create new encoder", e);
        } catch (ClassNotFoundException e) {
            throw new OrmException("Cannot create new encoder", e);
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> IndexFunction<T> cast(final Object c) {
        return (IndexFunction<T>) c;
    }

    private void init() {
        superTypeName = Type.getInternalName(IndexFunction.class);
        implClassName = pojo.getName() + "_IndexFunction_" + query.getName() + "_" + Util.createRandomName();
        implTypeName = implClassName.replace('.', '/');

        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_3, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, implTypeName, null, superTypeName, new String[] {});
    }

    private void implementConstructor() {
        final String consName = "<init>";
        final String consDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {});
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, consName, consDesc, null, null);
        mv.visitCode();

        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, superTypeName, consName, consDesc);

        mv.visitInsn(RETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private void implementGetName() {
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "getName",
                Type.getMethodDescriptor(Type.getType(String.class), new Type[] {}), null, null);
        mv.visitCode();
        mv.visitLdcInsn(query.getName());
        mv.visitInsn(ARETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private void implementIncludes() throws OrmException {
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "includes",
                Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[] { object }), null, null);
        mv.visitCode();
        final IncludeCGS cgs = new IncludeCGS(mv);
        cgs.setEntityType(pojoType);

        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
        mv.visitVarInsn(ASTORE, 1);

        Set<ColumnModel> checked = new HashSet<ColumnModel>();
        for (QueryModel.OrderBy orderby : myFields) {
            checkNotNullFields(Collections.singleton(orderby.column), checked, mv, cgs);
        }

        final Tree parseTree = query.getParseTree();
        if (parseTree != null) {
            checkConstants(parseTree, mv, cgs);
        }

        cgs.push(1);
        mv.visitInsn(IRETURN);

        mv.visitLabel(cgs.no);
        cgs.push(0);
        mv.visitInsn(IRETURN);

        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private static void checkNotNullFields(Collection<ColumnModel> myFields, Set<ColumnModel> checked,
            MethodVisitor mv, IncludeCGS cgs) throws OrmException {
        for (ColumnModel f : myFields) {
            if (f.isNested()) {
                checkNotNullFields(f.getNestedColumns(), checked, mv, cgs);
            } else {
                checkNotNullScalar(mv, checked, cgs, f);
            }
        }
    }

    private static void checkNotNullScalar(MethodVisitor mv, Set<ColumnModel> checked, IncludeCGS cgs,
            ColumnModel f) throws OrmException {
        checkParentNotNull(f.getParent(), checked, mv, cgs);
        cgs.setFieldReference(f);

        switch (Type.getType(f.getPrimitiveType()).getSort()) {
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.SHORT:
        case Type.CHAR:
        case Type.INT:
        case Type.LONG:
            break;

        case Type.ARRAY:
        case Type.OBJECT: {
            if (f.getPrimitiveType() == byte[].class) {
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNULL, cgs.no);

            } else if (f.getPrimitiveType() == String.class) {
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNULL, cgs.no);

            } else if (f.getPrimitiveType() == java.sql.Timestamp.class
                    || f.getPrimitiveType() == java.util.Date.class
                    || f.getPrimitiveType() == java.sql.Date.class) {
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNULL, cgs.no);

            } else {
                throw new OrmException(
                        "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
            }
            break;
        }

        default:
            throw new OrmException(
                    "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
        }
    }

    private static void checkParentNotNull(ColumnModel f, Set<ColumnModel> checked, MethodVisitor mv,
            IncludeCGS cgs) {
        if (f != null && checked.add(f)) {
            checkParentNotNull(f.getParent(), checked, mv, cgs);
            cgs.setFieldReference(f);
            cgs.pushFieldValue();
            mv.visitJumpInsn(IFNULL, cgs.no);
        }
    }

    private void checkConstants(Tree node, MethodVisitor mv, IncludeCGS cgs) throws OrmException {
        switch (node.getType()) {
        // These don't impact the constant evaluation
        case QueryParser.ORDER:
        case QueryParser.LIMIT:
            break;

        case 0: // nil node used to join other nodes together
        case QueryParser.WHERE:
        case QueryParser.AND:
            for (int i = 0; i < node.getChildCount(); i++) {
                checkConstants(node.getChild(i), mv, cgs);
            }
            break;

        case QueryParser.LT:
        case QueryParser.LE:
        case QueryParser.GT:
        case QueryParser.GE:
        case QueryParser.EQ: {
            final Tree lhs = node.getChild(0);
            final Tree rhs = node.getChild(1);
            if (lhs.getType() != QueryParser.ID) {
                throw new OrmException("Unsupported query token");
            }

            cgs.setFieldReference(((QueryParser.Column) lhs).getField());
            switch (rhs.getType()) {
            case QueryParser.PLACEHOLDER:
                // Parameter evaluated at runtime
                break;

            case QueryParser.TRUE:
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFEQ, cgs.no);
                break;

            case QueryParser.FALSE:
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNE, cgs.no);
                break;

            case QueryParser.CONSTANT_INTEGER:
                cgs.pushFieldValue();
                cgs.push(Integer.parseInt(rhs.getText()));
                mv.visitJumpInsn(IF_ICMPNE, cgs.no);
                break;

            case QueryParser.CONSTANT_STRING:
                if (cgs.getFieldReference().getPrimitiveType() == Character.TYPE) {
                    cgs.push(dequote(rhs.getText()).charAt(0));
                    cgs.pushFieldValue();
                    mv.visitJumpInsn(IF_ICMPNE, cgs.no);
                } else {
                    mv.visitLdcInsn(dequote(rhs.getText()));
                    cgs.pushFieldValue();
                    mv.visitMethodInsn(INVOKEVIRTUAL, string.getInternalName(), "equals",
                            Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[] { object }));
                    mv.visitJumpInsn(IFEQ, cgs.no);
                }
                break;
            }
            break;
        }

        default:
            throw new OrmException("Unsupported query token " + node.toStringTree());
        }
    }

    private static String dequote(String text) {
        return text.substring(1, text.length() - 1);
    }

    private void implementEncode() throws OrmException {
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "encode",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { indexKeyBuilder, object }), null, null);
        mv.visitCode();
        final EncodeCGS cgs = new EncodeCGS(mv);
        cgs.setEntityType(pojoType);

        mv.visitVarInsn(ALOAD, 2);
        mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
        mv.visitVarInsn(ASTORE, 2);

        encodeFields(myFields, mv, cgs);

        mv.visitInsn(RETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    static void encodeFields(Collection<QueryModel.OrderBy> myFields, final MethodVisitor mv, final EncodeCGS cgs)
            throws OrmException {
        Iterator<QueryModel.OrderBy> i = myFields.iterator();
        while (i.hasNext()) {
            QueryModel.OrderBy f = i.next();
            encodeScalar(f, mv, cgs);
            if (i.hasNext()) {
                cgs.delimiter();
            }
        }
    }

    static void encodeField(QueryModel.OrderBy f, final MethodVisitor mv, final EncodeCGS cgs) throws OrmException {
        if (f.column.isNested()) {
            encodeFields(orderByLeaves(Collections.singletonList(f)), mv, cgs);
        } else {
            encodeScalar(f, mv, cgs);
        }
    }

    private static void encodeScalar(QueryModel.OrderBy f, final MethodVisitor mv, final EncodeCGS cgs)
            throws OrmException {
        String method = f.descending ? "desc" : "add";
        ColumnModel c = f.column;
        cgs.setFieldReference(c);

        switch (Type.getType(c.getPrimitiveType()).getSort()) {
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.SHORT:
        case Type.CHAR:
        case Type.INT:
            cgs.pushBuilder();
            cgs.pushFieldValue();
            mv.visitInsn(I2L);
            mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), method,
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.LONG_TYPE }));
            break;

        case Type.LONG:
            cgs.pushBuilder();
            cgs.pushFieldValue();
            mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), method,
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.LONG_TYPE }));
            break;

        case Type.ARRAY:
        case Type.OBJECT: {
            if (c.getPrimitiveType() == byte[].class) {
                cgs.pushBuilder();
                cgs.pushFieldValue();
                mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), method,
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(byte[].class) }));

            } else if (c.getPrimitiveType() == String.class) {
                cgs.pushBuilder();
                cgs.pushFieldValue();
                mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), method,
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { string }));

            } else if (c.getPrimitiveType() == java.sql.Timestamp.class
                    || c.getPrimitiveType() == java.util.Date.class
                    || c.getPrimitiveType() == java.sql.Date.class) {
                cgs.pushBuilder();
                cgs.pushFieldValue();
                String tsType = Type.getType(c.getPrimitiveType()).getInternalName();
                mv.visitMethodInsn(INVOKEVIRTUAL, tsType, "getTime",
                        Type.getMethodDescriptor(Type.LONG_TYPE, new Type[] {}));
                mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), method,
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.LONG_TYPE }));
            } else {
                throw new OrmException(
                        "Type " + c.getPrimitiveType() + " not supported for field " + c.getPathToFieldName());
            }
            break;
        }

        default:
            throw new OrmException(
                    "Type " + c.getPrimitiveType() + " not supported for field " + c.getPathToFieldName());
        }
    }

    private static final class IncludeCGS extends CodeGenSupport {
        final Label no = new Label();

        private IncludeCGS(MethodVisitor method) {
            super(method);
        }

        @Override
        public void pushEntity() {
            mv.visitVarInsn(ALOAD, 1);
        }
    }

    static class EncodeCGS extends CodeGenSupport {
        EncodeCGS(MethodVisitor method) {
            super(method);
        }

        void infinity() {
            pushBuilder();
            mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), "infinity",
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
        }

        void delimiter() {
            pushBuilder();
            mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), "delimiter",
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
        }

        void nul() {
            pushBuilder();
            mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(), "nul",
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
        }

        void pushBuilder() {
            mv.visitVarInsn(ALOAD, 1);
        }

        @Override
        public void pushEntity() {
            mv.visitVarInsn(ALOAD, 2);
        }
    }
}