blob: b0a44de5861934765798c2095892c9f5d5d98efa [file] [log] [blame]
// 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<>();
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[] {});
}
}
}