/**
 * EasyBeans
 * Copyright (C) 2006 Bull S.A.S.
 * Contact: easybeans@objectweb.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: AnalyzerClassVisitor.java 5276 2010-01-08 16:22:04Z benoitf $
 * --------------------------------------------------------------------------
 */

package org.ow2.spec.testengine;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;
import org.ow2.spec.testengine.metadata.AnnotationMetadata;
import org.ow2.spec.testengine.metadata.ClassMetadata;
import org.ow2.spec.testengine.metadata.FieldMetadata;
import org.ow2.spec.testengine.metadata.InnerClassMetadata;
import org.ow2.spec.testengine.metadata.MethodMetadata;

public class AnalyzerClassVisitor implements ClassVisitor {

    /**
     * Map between package name and Map of <className, classMetadata>.
     */
    private Map<String, Map<String, ClassMetadata>> packageClassesMetadataMap = null;


    private ClassMetadata classMetaData = null;

    public AnalyzerClassVisitor(Map<String, Map<String, ClassMetadata>> packageClassesMetadataMap) {
        this.packageClassesMetadataMap = packageClassesMetadataMap;
    }

    /**
     * Visits the header of the class.
     *
     * @param version the class version.
     * @param access the class's access flags (see {@link Opcodes}). This
     *        parameter also indicates if the class is deprecated.
     * @param name the internal name of the class (see
     *        {@link Type#getInternalName() getInternalName}).
     * @param signature the signature of this class. May be <tt>null</tt> if
     *        the class is not a generic one, and does not extend or implement
     *        generic classes or interfaces.
     * @param superName the internal of name of the super class (see
     *        {@link Type#getInternalName() getInternalName}). For interfaces,
     *        the super class is {@link Object}. May be <tt>null</tt>, but
     *        only for the {@link Object} class.
     * @param interfaces the internal names of the class's interfaces (see
     *        {@link Type#getInternalName() getInternalName}). May be
     *        <tt>null</tt>.
     */
    public void visit(
            int version,
            int access,
            String name,
            String signature,
            String superName,
            String[] interfaces) {

        // Build class representation
        classMetaData = new ClassMetadata(access, name, signature, superName, interfaces);
    }

    /**
     * Visits the source of the class.
     *
     * @param source the name of the source file from which the class was
     *        compiled. May be <tt>null</tt>.
     * @param debug additional debug information to compute the correspondance
     *        between source and compiled elements of the class. May be
     *        <tt>null</tt>.
     */
    public void visitSource(String source, String debug) {

    }

    /**
     * Visits the enclosing class of the class. This method must be called only
     * if the class has an enclosing class.
     *
     * @param owner internal name of the enclosing class of the class.
     * @param name the name of the method that contains the class, or
     *        <tt>null</tt> if the class is not enclosed in a method of its
     *        enclosing class.
     * @param desc the descriptor of the method that contains the class, or
     *        <tt>null</tt> if the class is not enclosed in a method of its
     *        enclosing class.
     */
    public void visitOuterClass(String owner, String name, String desc) {

    }

    /**
     * Visits an annotation of the class.
     *
     * @param desc the class descriptor of the annotation class.
     * @param visible <tt>true</tt> if the annotation is visible at runtime.
     * @return a non null visitor to visit the annotation values.
     */
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        AnnotationMetadata annotationMetadata = new AnnotationMetadata(desc, visible);
        classMetaData.addAnnotationMetadata(annotationMetadata);
        return new AnalyzerAnnotationVisitor(annotationMetadata);
    }

    /**
     * Visits a non standard attribute of the class.
     *
     * @param attr an attribute.
     */
    public void visitAttribute(Attribute attr) {

    }

    /**
     * Visits information about an inner class. This inner class is not
     * necessarily a member of the class being visited.
     *
     * @param name the internal name of an inner class (see
     *        {@link Type#getInternalName() getInternalName}).
     * @param outerName the internal name of the class to which the inner class
     *        belongs (see {@link Type#getInternalName() getInternalName}). May
     *        be <tt>null</tt>.
     * @param innerName the (simple) name of the inner class inside its
     *        enclosing class. May be <tt>null</tt> for anonymous inner
     *        classes.
     * @param access the access flags of the inner class as originally declared
     *        in the enclosing class.
     */
    public void visitInnerClass(
            String name,
            String outerName,
            String innerName,
            int access) {
        // Not private, log it
        if ((access & Opcodes.ACC_PRIVATE) == 0) {
            InnerClassMetadata innerClassMetadata = new InnerClassMetadata(name, outerName, innerName, access);
            classMetaData.addInnerClassMetadata(innerClassMetadata);
        }
    }

    /**
     * Visits a field of the class.
     *
     * @param access the field's access flags (see {@link Opcodes}). This
     *        parameter also indicates if the field is synthetic and/or
     *        deprecated.
     * @param name the field's name.
     * @param desc the field's descriptor (see {@link Type Type}).
     * @param signature the field's signature. May be <tt>null</tt> if the
     *        field's type does not use generic types.
     * @param value the field's initial value. This parameter, which may be
     *        <tt>null</tt> if the field does not have an initial value, must
     *        be an {@link Integer}, a {@link Float}, a {@link Long}, a
     *        {@link Double} or a {@link String} (for <tt>int</tt>,
     *        <tt>float</tt>, <tt>long</tt> or <tt>String</tt> fields
     *        respectively). <i>This parameter is only used for static fields</i>.
     *        Its value is ignored for non static fields, which must be
     *        initialized through bytecode instructions in constructors or
     *        methods.
     * @return a visitor to visit field annotations and attributes, or
     *         <tt>null</tt> if this class visitor is not interested in
     *         visiting these annotations and attributes.
     */
    public FieldVisitor visitField(
            int access,
            String name,
            String desc,
            String signature,
            Object value) {

        // Not private, log it
        if ((access & Opcodes.ACC_PRIVATE) == 0) {
            FieldMetadata fieldMetadata = new FieldMetadata(access, name, desc, signature, value);
            classMetaData.addFieldMetadata(fieldMetadata);
        }
        return new EmptyVisitor();
    }

    /**
     * Visits a method of the class. This method <i>must</i> return a new
     * {@link MethodVisitor} instance (or <tt>null</tt>) each time it is
     * called, i.e., it should not return a previously returned visitor.
     *
     * @param access the method's access flags (see {@link Opcodes}). This
     *        parameter also indicates if the method is synthetic and/or
     *        deprecated.
     * @param name the method's name.
     * @param desc the method's descriptor (see {@link Type Type}).
     * @param signature the method's signature. May be <tt>null</tt> if the
     *        method parameters, return type and exceptions do not use generic
     *        types.
     * @param exceptions the internal names of the method's exception classes
     *        (see {@link Type#getInternalName() getInternalName}). May be
     *        <tt>null</tt>.
     * @return an object to visit the byte code of the method, or <tt>null</tt>
     *         if this class visitor is not interested in visiting the code of
     *         this method.
     */
    public MethodVisitor visitMethod(
            int access,
            String name,
            String desc,
            String signature,
            String[] exceptions) {

        // If enum type and JDK 5.0, change access to
        if ("java/lang/Enum".equals(classMetaData.getSuperName())) {
            if ("values".equals(name)) {
                access = 25;
            }

        }
        // Not private, log it
        if ((access & Opcodes.ACC_PRIVATE) == 0) {
            MethodMetadata methodMetadata = new MethodMetadata(access, name, desc, signature, exceptions);
            classMetaData.addMethodMetadata(methodMetadata);
        }
        return new EmptyVisitor();
    }

    /**
     * Visits the end of the class. This method, which is the last one to be
     * called, is used to inform the visitor that all the fields and methods of
     * the class have been visited.
     */
    public void visitEnd() {

        String packageName = "";
        String[] split = classMetaData.getName().split("/");
        packageName += split[0];
        packageName += ".";
        packageName += split[1];


        Map<String, ClassMetadata> classMetadataMap = packageClassesMetadataMap.get(packageName);
        if (classMetadataMap == null) {
            classMetadataMap = new HashMap<String, ClassMetadata>();
            packageClassesMetadataMap.put(packageName, classMetadataMap);
        }

        classMetadataMap.put(classMetaData.getName(), classMetaData);
    }

    public void setUID(long uid) {
        classMetaData.setUID(uid);
    }
}
