Java tutorial
// Copyright 2008 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.server; import com.google.gwtorm.schema.RelationModel; import com.google.gwtorm.schema.SchemaModel; import com.google.gwtorm.schema.SequenceModel; import com.google.gwtorm.schema.Util; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** Generates a concrete implementation of a {@link Schema} extension. */ public class SchemaGen<S extends AbstractSchema> implements Opcodes { public interface AccessGenerator { Class<?> create(GeneratedClassLoader loader, RelationModel rm) throws OrmException; } private final GeneratedClassLoader classLoader; private final SchemaModel schema; private final Class<?> databaseClass; private final Class<S> schemaSuperClass; private final AccessGenerator accessGen; private List<RelationGen> relations; private ClassWriter cw; private String implClassName; private String implTypeName; public SchemaGen(final GeneratedClassLoader loader, final SchemaModel schemaModel, final Class<?> databaseType, final Class<S> superType, final AccessGenerator ag) { classLoader = loader; schema = schemaModel; databaseClass = databaseType; schemaSuperClass = superType; accessGen = ag; } public Class<Schema> create() throws OrmException { defineRelationClasses(); init(); implementRelationFields(); implementConstructor(); implementSequenceMethods(); implementRelationMethods(); implementAllRelationsMethod(); cw.visitEnd(); classLoader.defineClass(getImplClassName(), cw.toByteArray()); return loadClass(); } @SuppressWarnings("unchecked") private Class<Schema> loadClass() throws OrmException { try { final Class<?> c = Class.forName(getImplClassName(), false, classLoader); return (Class<Schema>) c; } catch (ClassNotFoundException err) { throw new OrmException("Cannot load generated class", err); } } String getSchemaClassName() { return schema.getSchemaClassName(); } String getImplClassName() { return implClassName; } String getImplTypeName() { return implTypeName; } private void defineRelationClasses() throws OrmException { relations = new ArrayList<RelationGen>(); for (final RelationModel rel : schema.getRelations()) { final Class<?> a = accessGen.create(classLoader, rel); relations.add(new RelationGen(rel, a)); } Collections.sort(relations, new Comparator<RelationGen>() { @Override public int compare(RelationGen a, RelationGen b) { int cmp = a.model.getRelationID() - b.model.getRelationID(); if (cmp == 0) { cmp = a.model.getRelationName().compareTo(b.model.getRelationName()); } return cmp; } }); } private void init() { implClassName = getSchemaClassName() + "_Schema_" + Util.createRandomName(); implTypeName = implClassName.replace('.', '/'); cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); cw.visit(V1_3, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, implTypeName, null, Type.getInternalName(schemaSuperClass), new String[] { getSchemaClassName().replace('.', '/') }); } private void implementRelationFields() { for (final RelationGen info : relations) { info.implementField(); } } private void implementConstructor() { final String consName = "<init>"; final Type superType = Type.getType(schemaSuperClass); final Type dbType = Type.getType(databaseClass); final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, consName, Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { dbType }), null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, superType.getInternalName(), consName, Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType(schemaSuperClass.getDeclaredConstructors()[0].getParameterTypes()[0]) })); for (final RelationGen info : relations) { mv.visitVarInsn(ALOAD, 0); mv.visitTypeInsn(NEW, info.accessType.getInternalName()); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, info.accessType.getInternalName(), consName, Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { superType })); mv.visitFieldInsn(PUTFIELD, implTypeName, info.getAccessInstanceFieldName(), info.accessType.getDescriptor()); } mv.visitInsn(RETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); } private void implementSequenceMethods() { for (final SequenceModel seq : schema.getSequences()) { final Type retType = Type.getType(seq.getResultType()); final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, seq.getMethodName(), Type.getMethodDescriptor(retType, new Type[] {}), null, new String[] { Type.getType(OrmException.class).getInternalName() }); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn(seq.getSequenceName()); mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(schemaSuperClass), "nextLong", Type.getMethodDescriptor(Type.getType(Long.TYPE), new Type[] { Type.getType(String.class) })); if (retType.getSize() == 1) { mv.visitInsn(L2I); mv.visitInsn(IRETURN); } else { mv.visitInsn(LRETURN); } mv.visitMaxs(-1, -1); mv.visitEnd(); } } private void implementRelationMethods() { for (final RelationGen info : relations) { info.implementMethod(); } } private void implementAllRelationsMethod() { final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "allRelations", Type.getMethodDescriptor(Type.getType(Access[].class), new Type[] {}), null, null); mv.visitCode(); final int r = 1; CodeGenSupport cgs = new CodeGenSupport(mv); cgs.push(relations.size()); mv.visitTypeInsn(ANEWARRAY, Type.getType(Access.class).getInternalName()); mv.visitVarInsn(ASTORE, r); int index = 0; for (RelationGen info : relations) { mv.visitVarInsn(ALOAD, r); cgs.push(index++); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, getImplTypeName(), info.model.getMethodName(), info.getDescriptor()); mv.visitInsn(AASTORE); } mv.visitVarInsn(ALOAD, r); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); } private class RelationGen { final RelationModel model; final Type accessType; RelationGen(final RelationModel model, final Class<?> accessClass) { this.model = model; this.accessType = Type.getType(accessClass); } void implementField() { cw.visitField(ACC_PRIVATE | ACC_FINAL, getAccessInstanceFieldName(), accessType.getDescriptor(), null, null).visitEnd(); } String getAccessInstanceFieldName() { return "access_" + model.getMethodName(); } void implementMethod() { final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, model.getMethodName(), getDescriptor(), null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, implTypeName, getAccessInstanceFieldName(), accessType.getDescriptor()); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); } String getDescriptor() { return Type.getMethodDescriptor(Type.getObjectType(model.getAccessInterfaceName().replace('.', '/')), new Type[] {}); } } }