Java tutorial
// 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); } } }