com.google.gwtorm.server.SchemaGen.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwtorm.server.SchemaGen.java

Source

// 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[] {});
        }
    }
}