/*******************************************************************************
 * Copyright (c) 1998, 2008 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/

package org.eclipse.persistence.internal.dynamicpersist;

// Javase imports

// Java extension imports

// EclipseLink imports
import org.eclipse.persistence.internal.libraries.asm.ClassWriter;
import org.eclipse.persistence.internal.libraries.asm.CodeVisitor;
import static org.eclipse.persistence.internal.libraries.asm.Constants.ACC_PUBLIC;
import static org.eclipse.persistence.internal.libraries.asm.Constants.ACC_STATIC;
import static org.eclipse.persistence.internal.libraries.asm.Constants.ACC_SUPER;
import static org.eclipse.persistence.internal.libraries.asm.Constants.ALOAD;
import static org.eclipse.persistence.internal.libraries.asm.Constants.ANEWARRAY;
import static org.eclipse.persistence.internal.libraries.asm.Constants.GETSTATIC;
import static org.eclipse.persistence.internal.libraries.asm.Constants.ICONST_1;
import static org.eclipse.persistence.internal.libraries.asm.Constants.INVOKESPECIAL;
import static org.eclipse.persistence.internal.libraries.asm.Constants.INVOKESTATIC;
import static org.eclipse.persistence.internal.libraries.asm.Constants.INVOKEVIRTUAL;
import static org.eclipse.persistence.internal.libraries.asm.Constants.IRETURN;
import static org.eclipse.persistence.internal.libraries.asm.Constants.PUTFIELD;
import static org.eclipse.persistence.internal.libraries.asm.Constants.PUTSTATIC;
import static org.eclipse.persistence.internal.libraries.asm.Constants.RETURN;
import static org.eclipse.persistence.internal.libraries.asm.Constants.V1_5;

/**
 * <p>
 * <b>INTERNAL:</b> BaseEntitySubClassLoader uses ASM to dynamically
 * generate subclasses of {@link BaseEntity}
 *
 * @author Mike Norman - michael.norman@oracle.com
 * @since EclipseLink 1.x
 */

public class BaseEntityClassLoader extends ClassLoader {

    private static final String BASE_ENTITY_CLASSNAME_SLASHES =
        BaseEntity.class.getName().replace('.', '/');
    
    public static final String COLLECTION_WRAPPER_SUFFIX = 
        "_CollectionWrapper";
    private static final String BASE_ENTITY_COLLECTION_WRAPPER_CLASSNAME_SLASHES =
        BaseEntityCollectionWrapper.class.getName().replace('.', '/');

    protected boolean generateSubclasses = true;

    public BaseEntityClassLoader() {
        this(BaseEntityClassLoader.class.getClassLoader());
    }

    public BaseEntityClassLoader(ClassLoader parentLoader) {
        super(parentLoader);
    }

    protected Class<?> findClass(String className) throws ClassNotFoundException {

        if (!generateSubclasses) {
            throw new ClassNotFoundException(className);
        }
        try {
            byte[] data = generateClassBytes(className);
            return defineClass(className, data, 0, data.length);
        } catch (ClassFormatError e) {
            throw new ClassNotFoundException(className, e);
        }
    }

    protected byte[] generateClassBytes(String className) {
        /*
         * Pattern is as follows: public class Foo extends BaseEntity {
         *
         * public static int NUM_ATTRIBUTES = 1;
         *
         * public static void setNumAttributes(Integer numAttributes) {
         *   NUM_ATTRIBUTES = numAttributes.intValue();
         *   }
         *   public static int getNumAttributes() {
         *     return NUM_ATTRIBUTES;
         *   }
         *
         * public Foo() {
         *   super();
         *   fields = new Object[getNumAttributes()];
         *   }
         * }
         * Later, via reflection, the setNumAttributes method is called with the
         * correct number of attributes
         */
        String classNameAsSlashes = className.replace('.', '/');
        ClassWriter cw = new ClassWriter(true);
        CodeVisitor cv;

        if (className.endsWith(COLLECTION_WRAPPER_SUFFIX)) {
            cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, classNameAsSlashes,
                BASE_ENTITY_COLLECTION_WRAPPER_CLASSNAME_SLASHES, null, null);

            cv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            cv.visitVarInsn(ALOAD, 0);
            cv.visitMethodInsn(INVOKESPECIAL, BASE_ENTITY_COLLECTION_WRAPPER_CLASSNAME_SLASHES,
                "<init>", "()V");
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
        }

        else {
            cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, classNameAsSlashes, BASE_ENTITY_CLASSNAME_SLASHES,
                null, null);
           
            cw.visitField(ACC_PUBLIC + ACC_STATIC, "NUM_ATTRIBUTES", "I", null, null);
            cv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            cv.visitInsn(ICONST_1);
            cv.visitFieldInsn(PUTSTATIC, classNameAsSlashes, "NUM_ATTRIBUTES", "I");
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
    
            cv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "setNumAttributes", "(Ljava/lang/Integer;)V",
                null, null);
            cv.visitVarInsn(ALOAD, 0);
            cv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
            cv.visitFieldInsn(PUTSTATIC, classNameAsSlashes, "NUM_ATTRIBUTES", "I");
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
    
            cv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "getNumAttributes", "()I", null, null);
            cv.visitFieldInsn(GETSTATIC, classNameAsSlashes, "NUM_ATTRIBUTES", "I");
            cv.visitInsn(IRETURN);
            cv.visitMaxs(0, 0);
    
            cv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            cv.visitVarInsn(ALOAD, 0);
            cv.visitMethodInsn(INVOKESPECIAL, BASE_ENTITY_CLASSNAME_SLASHES, "<init>", "()V");
            cv.visitVarInsn(ALOAD, 0);
            cv.visitMethodInsn(INVOKESTATIC, classNameAsSlashes, "getNumAttributes", "()I");
            cv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
            cv.visitFieldInsn(PUTFIELD, classNameAsSlashes, "fields", "[Ljava/lang/Object;");
            cv.visitInsn(RETURN);
            cv.visitMaxs(0, 0);
        }
        cw.visitEnd();
        return cw.toByteArray();
    }

    public void dontGenerateSubclasses() {
        generateSubclasses = false;
    }
}